search
This commit is contained in:
@@ -5,7 +5,6 @@ pub mod text_editor;
|
|||||||
pub mod background;
|
pub mod background;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
pub mod autocomplete;
|
pub mod autocomplete;
|
||||||
pub mod search_palette;
|
|
||||||
pub mod find_file_palette;
|
pub mod find_file_palette;
|
||||||
|
|
||||||
pub use command_line::*;
|
pub use command_line::*;
|
||||||
@@ -14,5 +13,4 @@ pub use text_editor::*;
|
|||||||
pub use background::*;
|
pub use background::*;
|
||||||
pub use dialog::*;
|
pub use dialog::*;
|
||||||
pub use autocomplete::*;
|
pub use autocomplete::*;
|
||||||
pub use search_palette::*;
|
|
||||||
pub use find_file_palette::*;
|
pub use find_file_palette::*;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub mod services;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod sidebar;
|
pub mod sidebar;
|
||||||
|
pub mod search;
|
||||||
|
|
||||||
pub use ui::run_ui;
|
pub use ui::run_ui;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::config::binds::config::Config;
|
|||||||
use crate::config::binds::key_sequences::KeySequenceTracker;
|
use crate::config::binds::key_sequences::KeySequenceTracker;
|
||||||
use crate::buffer::{AppView, BufferState, switch_buffer, functions, toggle_buffer_list};
|
use crate::buffer::{AppView, BufferState, switch_buffer, functions, toggle_buffer_list};
|
||||||
use crate::sidebar::toggle_sidebar;
|
use crate::sidebar::toggle_sidebar;
|
||||||
|
use crate::search::event::handle_search_palette_event;
|
||||||
use crate::functions::modes::navigation::add_logic_nav;
|
use crate::functions::modes::navigation::add_logic_nav;
|
||||||
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
|
use crate::functions::modes::navigation::add_logic_nav::SaveLogicResultSender;
|
||||||
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
use crate::functions::modes::navigation::add_table_nav::SaveTableResultSender;
|
||||||
@@ -20,7 +21,6 @@ use crate::services::grpc_client::GrpcClient;
|
|||||||
use canvas::{FormEditor, AppMode as CanvasMode};
|
use canvas::{FormEditor, AppMode as CanvasMode};
|
||||||
use crate::state::{
|
use crate::state::{
|
||||||
app::{
|
app::{
|
||||||
search::SearchState,
|
|
||||||
state::AppState,
|
state::AppState,
|
||||||
},
|
},
|
||||||
pages::{
|
pages::{
|
||||||
@@ -30,6 +30,7 @@ use crate::state::{
|
|||||||
intro::IntroState,
|
intro::IntroState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use crate::search::state::SearchState;
|
||||||
use crate::tui::functions::common::login::LoginResult;
|
use crate::tui::functions::common::login::LoginResult;
|
||||||
use crate::tui::functions::common::register::RegisterResult;
|
use crate::tui::functions::common::register::RegisterResult;
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
@@ -215,132 +216,6 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function handles state changes.
|
|
||||||
async fn handle_search_palette_event(
|
|
||||||
&mut self,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
app_state: &mut AppState,
|
|
||||||
) -> Result<EventOutcome> {
|
|
||||||
let mut should_close = false;
|
|
||||||
let mut outcome_message = String::new();
|
|
||||||
let mut trigger_search = false;
|
|
||||||
|
|
||||||
// Step 1: Handle search_state logic in a short scope
|
|
||||||
let (maybe_data, maybe_id) = {
|
|
||||||
if let Some(search_state) = app_state.search_state.as_mut() {
|
|
||||||
match key_event.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
should_close = true;
|
|
||||||
outcome_message = "Search cancelled".to_string();
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
if let Some(selected_hit) =
|
|
||||||
search_state.results.get(search_state.selected_index)
|
|
||||||
{
|
|
||||||
if let Ok(data) = serde_json::from_str::<
|
|
||||||
std::collections::HashMap<String, String>,
|
|
||||||
>(&selected_hit.content_json)
|
|
||||||
{
|
|
||||||
(Some(data), Some(selected_hit.id))
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
|
||||||
search_state.previous_result();
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
search_state.next_result();
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Char(c) => {
|
|
||||||
search_state.input.insert(search_state.cursor_position, c);
|
|
||||||
search_state.cursor_position += 1;
|
|
||||||
trigger_search = true;
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
if search_state.cursor_position > 0 {
|
|
||||||
search_state.cursor_position -= 1;
|
|
||||||
search_state.input.remove(search_state.cursor_position);
|
|
||||||
trigger_search = true;
|
|
||||||
}
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Left => {
|
|
||||||
search_state.cursor_position =
|
|
||||||
search_state.cursor_position.saturating_sub(1);
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
KeyCode::Right => {
|
|
||||||
if search_state.cursor_position < search_state.input.len() {
|
|
||||||
search_state.cursor_position += 1;
|
|
||||||
}
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
_ => (None, None),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step 2: Now safe to borrow form_state
|
|
||||||
if let (Some(data), Some(id)) = (maybe_data, maybe_id) {
|
|
||||||
if let Some(fs) = app_state.form_state_mut() {
|
|
||||||
let detached_pos = fs.total_count + 2;
|
|
||||||
fs.update_from_response(&data, detached_pos);
|
|
||||||
}
|
|
||||||
should_close = true;
|
|
||||||
outcome_message = format!("Loaded record ID {}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Trigger async search if needed
|
|
||||||
if trigger_search {
|
|
||||||
if let Some(search_state) = app_state.search_state.as_mut() {
|
|
||||||
search_state.is_loading = true;
|
|
||||||
search_state.results.clear();
|
|
||||||
search_state.selected_index = 0;
|
|
||||||
|
|
||||||
let query = search_state.input.clone();
|
|
||||||
let table_name = search_state.table_name.clone();
|
|
||||||
let sender = self.search_result_sender.clone();
|
|
||||||
let mut grpc_client = self.grpc_client.clone();
|
|
||||||
|
|
||||||
info!("--- 1. Spawning search task for query: '{}' ---", query);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
info!("--- 2. Background task started. ---");
|
|
||||||
match grpc_client.search_table(table_name, query).await {
|
|
||||||
Ok(response) => {
|
|
||||||
info!(
|
|
||||||
"--- 3a. gRPC call successful. Found {} hits. ---",
|
|
||||||
response.hits.len()
|
|
||||||
);
|
|
||||||
let _ = sender.send(response.hits);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
error!("--- 3b. gRPC call failed: {:?} ---", e);
|
|
||||||
let _ = sender.send(vec![]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_close {
|
|
||||||
app_state.search_state = None;
|
|
||||||
app_state.ui.show_search_palette = false;
|
|
||||||
app_state.ui.focus_outside_canvas = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(EventOutcome::Ok(outcome_message))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn handle_event(
|
pub async fn handle_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -358,9 +233,16 @@ impl EventHandler {
|
|||||||
) -> Result<EventOutcome> {
|
) -> Result<EventOutcome> {
|
||||||
if app_state.ui.show_search_palette {
|
if app_state.ui.show_search_palette {
|
||||||
if let Event::Key(key_event) = event {
|
if let Event::Key(key_event) = event {
|
||||||
return self.handle_search_palette_event(key_event, app_state).await;
|
if let Some(message) = handle_search_palette_event(
|
||||||
|
key_event,
|
||||||
|
app_state,
|
||||||
|
&mut self.grpc_client,
|
||||||
|
self.search_result_sender.clone(),
|
||||||
|
).await? {
|
||||||
|
return Ok(EventOutcome::Ok(message));
|
||||||
|
}
|
||||||
|
return Ok(EventOutcome::Ok(String::new()));
|
||||||
}
|
}
|
||||||
return Ok(EventOutcome::Ok(String::new()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current_mode =
|
let mut current_mode =
|
||||||
|
|||||||
@@ -7,4 +7,3 @@ pub mod canvas;
|
|||||||
pub use handlers::*;
|
pub use handlers::*;
|
||||||
pub use general::*;
|
pub use general::*;
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use canvas::*;
|
|
||||||
|
|||||||
107
client/src/search/event.rs
Normal file
107
client/src/search/event.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// src/search/event.rs
|
||||||
|
use crate::search::state::SearchState;
|
||||||
|
use crate::state::app::state::AppState;
|
||||||
|
use crate::services::grpc_client::GrpcClient;
|
||||||
|
use common::proto::komp_ac::search::search_response::Hit;
|
||||||
|
use crossterm::event::KeyCode;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tracing::{error, info};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub async fn handle_search_palette_event(
|
||||||
|
key_event: crossterm::event::KeyEvent,
|
||||||
|
app_state: &mut AppState,
|
||||||
|
grpc_client: &mut GrpcClient,
|
||||||
|
search_result_sender: mpsc::UnboundedSender<Vec<Hit>>,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
|
let mut should_close = false;
|
||||||
|
let mut outcome_message = None;
|
||||||
|
let mut trigger_search = false;
|
||||||
|
|
||||||
|
if let Some(search_state) = app_state.search_state.as_mut() {
|
||||||
|
match key_event.code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
should_close = true;
|
||||||
|
outcome_message = Some("Search cancelled".to_string());
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
// Step 1: Extract the data we need while holding the borrow
|
||||||
|
let maybe_data = search_state
|
||||||
|
.results
|
||||||
|
.get(search_state.selected_index)
|
||||||
|
.map(|hit| (hit.id, hit.content_json.clone()));
|
||||||
|
|
||||||
|
// Step 2: Process outside the borrow
|
||||||
|
if let Some((id, content_json)) = maybe_data {
|
||||||
|
if let Ok(data) = serde_json::from_str::<HashMap<String, String>>(&content_json) {
|
||||||
|
if let Some(fs) = app_state.form_state_mut() {
|
||||||
|
let detached_pos = fs.total_count + 2;
|
||||||
|
fs.update_from_response(&data, detached_pos);
|
||||||
|
}
|
||||||
|
should_close = true;
|
||||||
|
outcome_message = Some(format!("Loaded record ID {}", id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up => search_state.previous_result(),
|
||||||
|
KeyCode::Down => search_state.next_result(),
|
||||||
|
KeyCode::Char(c) => {
|
||||||
|
search_state.input.insert(search_state.cursor_position, c);
|
||||||
|
search_state.cursor_position += 1;
|
||||||
|
trigger_search = true;
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
if search_state.cursor_position > 0 {
|
||||||
|
search_state.cursor_position -= 1;
|
||||||
|
search_state.input.remove(search_state.cursor_position);
|
||||||
|
trigger_search = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
search_state.cursor_position =
|
||||||
|
search_state.cursor_position.saturating_sub(1);
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
if search_state.cursor_position < search_state.input.len() {
|
||||||
|
search_state.cursor_position += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if trigger_search {
|
||||||
|
if let Some(search_state) = app_state.search_state.as_mut() {
|
||||||
|
search_state.is_loading = true;
|
||||||
|
search_state.results.clear();
|
||||||
|
search_state.selected_index = 0;
|
||||||
|
|
||||||
|
let query = search_state.input.clone();
|
||||||
|
let table_name = search_state.table_name.clone();
|
||||||
|
let sender = search_result_sender.clone();
|
||||||
|
let mut grpc_client = grpc_client.clone();
|
||||||
|
|
||||||
|
info!("Spawning search task for query: '{}'", query);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match grpc_client.search_table(table_name, query).await {
|
||||||
|
Ok(response) => {
|
||||||
|
let _ = sender.send(response.hits);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Search failed: {:?}", e);
|
||||||
|
let _ = sender.send(vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_close {
|
||||||
|
app_state.search_state = None;
|
||||||
|
app_state.ui.show_search_palette = false;
|
||||||
|
app_state.ui.focus_outside_canvas = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(outcome_message)
|
||||||
|
}
|
||||||
7
client/src/search/mod.rs
Normal file
7
client/src/search/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// src/search/mod.rs
|
||||||
|
|
||||||
|
pub mod state;
|
||||||
|
pub mod ui;
|
||||||
|
pub mod event;
|
||||||
|
|
||||||
|
pub use ui::*;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// src/state/app/search.rs
|
// src/search/state.rs
|
||||||
|
|
||||||
use common::proto::komp_ac::search::search_response::Hit;
|
use common::proto::komp_ac::search::search_response::Hit;
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// src/components/common/search_palette.rs
|
// src/search/ui.rs
|
||||||
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::app::search::SearchState;
|
use crate::search::state::SearchState;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Modifier, Style},
|
style::{Modifier, Style},
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// src/state/app.rs
|
// src/state/app.rs
|
||||||
|
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod search;
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use common::proto::komp_ac::table_definition::ProfileTreeResponse;
|
|||||||
// NEW: Import the types we need for the cache
|
// NEW: Import the types we need for the cache
|
||||||
use common::proto::komp_ac::table_structure::TableStructureResponse;
|
use common::proto::komp_ac::table_structure::TableStructureResponse;
|
||||||
use crate::modes::handlers::mode_manager::AppMode;
|
use crate::modes::handlers::mode_manager::AppMode;
|
||||||
use crate::state::app::search::SearchState;
|
use crate::search::state::SearchState;
|
||||||
use crate::ui::handlers::context::DialogPurpose;
|
use crate::ui::handlers::context::DialogPurpose;
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use crate::config::binds::Config;
|
use crate::config::binds::Config;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::components::{
|
|||||||
auth::{login::render_login, register::render_register},
|
auth::{login::render_login, register::render_register},
|
||||||
common::dialog::render_dialog,
|
common::dialog::render_dialog,
|
||||||
common::find_file_palette,
|
common::find_file_palette,
|
||||||
common::search_palette::render_search_palette,
|
|
||||||
intro::intro::render_intro,
|
intro::intro::render_intro,
|
||||||
render_background,
|
render_background,
|
||||||
render_command_line,
|
render_command_line,
|
||||||
@@ -14,6 +13,7 @@ use crate::components::{
|
|||||||
};
|
};
|
||||||
use crate::sidebar::{calculate_sidebar_layout, render_sidebar};
|
use crate::sidebar::{calculate_sidebar_layout, render_sidebar};
|
||||||
use crate::buffer::render_buffer_list;
|
use crate::buffer::render_buffer_list;
|
||||||
|
use crate::search::render_search_palette;
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::modes::general::command_navigation::NavigationState;
|
use crate::modes::general::command_navigation::NavigationState;
|
||||||
use crate::buffer::state::BufferState;
|
use crate::buffer::state::BufferState;
|
||||||
|
|||||||
Reference in New Issue
Block a user