now no need for init_form_editor everywhere

This commit is contained in:
Priec
2025-08-21 21:16:59 +02:00
parent f2b426851b
commit 3dd6808ea2
4 changed files with 176 additions and 118 deletions

View File

@@ -7,8 +7,9 @@ use common::proto::komp_ac::table_structure::TableStructureResponse;
use crate::modes::handlers::mode_manager::AppMode;
use crate::state::app::search::SearchState;
use crate::ui::handlers::context::DialogPurpose;
use canvas::FormEditor;
use crate::state::pages::form::FormState;
use crate::config::binds::Config;
use canvas::FormEditor;
use std::collections::HashMap;
use std::env;
use std::sync::Arc;
@@ -185,6 +186,29 @@ impl AppState {
.get(self.ui.dialog.dialog_active_button_index)
.map(|s| s.as_str())
}
pub fn init_form_editor(&mut self, form_state: FormState, config: &Config) {
let mut editor = FormEditor::new(form_state);
editor.set_keymap(config.build_canvas_keymap()); // inject keymap
self.form_editor = Some(editor);
}
/// Replace the current form state and wrap it in a FormEditor with keymap
pub fn set_form_state(&mut self, form_state: FormState, config: &Config) {
let mut editor = FormEditor::new(form_state);
editor.set_keymap(config.build_canvas_keymap());
self.form_editor = Some(editor);
}
/// Immutable access to the underlying FormState
pub fn form_state(&self) -> Option<&FormState> {
self.form_editor.as_ref().map(|e| e.data_provider())
}
/// Mutable access to the underlying FormState
pub fn form_state_mut(&mut self) -> Option<&mut FormState> {
self.form_editor.as_mut().map(|e| e.data_provider_mut())
}
}
impl Default for UiState {

View File

@@ -116,26 +116,6 @@ impl FormState {
}
}
pub fn render(
&self,
f: &mut Frame,
area: Rect,
theme: &Theme,
is_edit_mode: bool,
) {
// Wrap in FormEditor for new API
let mut editor = FormEditor::new(self.clone());
// Use new canvas rendering
canvas::render_canvas_default(f, area, &editor);
// If autocomplete is active, render suggestions
if self.autocomplete_active && !self.autocomplete_suggestions.is_empty() {
// Note: This will need to be updated when suggestions are integrated
// canvas::render_suggestions_dropdown(f, area, input_rect, &canvas::DefaultCanvasTheme, &editor);
}
}
pub fn reset_to_empty(&mut self) {
self.id = 0;
self.values.iter_mut().for_each(|v| v.clear());

View File

@@ -24,6 +24,7 @@ use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState;
use crate::state::pages::form::FormState;
use crate::state::pages::intro::IntroState;
use crate::components::render_form;
use ratatui::{
layout::{Constraint, Direction, Layout},
Frame,
@@ -192,12 +193,15 @@ pub fn render_ui(
.split(form_actual_area)[1]
};
// CHANGED: Convert local HighlightState to canvas HighlightState for FormState
form_state.render(
render_form(
f,
form_render_area,
app_state,
form_state,
app_state.current_view_table_name.as_deref().unwrap_or(""),
theme,
is_event_handler_edit_mode,
form_state.total_count,
form_state.current_position,
);
}

View File

@@ -102,13 +102,15 @@ pub async fn run_ui() -> Result<()> {
})
.collect();
let mut form_state = FormState::new(
initial_profile.clone(),
initial_table.clone(),
initial_field_defs,
// Replace local form_state with app_state.form_editor
app_state.set_form_state(
FormState::new(initial_profile.clone(), initial_table.clone(), initial_field_defs),
&config,
);
UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state)
// Fetch initial count using app_state accessor
if let Some(form_state) = app_state.form_state_mut() {
UiService::fetch_and_set_table_count(&mut grpc_client, form_state)
.await
.context(format!(
"Failed to fetch initial count for table {}.{}",
@@ -116,12 +118,13 @@ pub async fn run_ui() -> Result<()> {
))?;
if form_state.total_count > 0 {
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, form_state).await {
event_handler.command_message = format!("Error loading initial data: {}", e);
}
} else {
form_state.reset_to_empty();
}
}
if auto_logged_in {
buffer_state.history = vec![AppView::Form];
@@ -137,7 +140,9 @@ pub async fn run_ui() -> Result<()> {
let mut table_just_switched = false;
loop {
let position_before_event = form_state.current_position;
let position_before_event = app_state.form_state()
.map(|fs| fs.current_position)
.unwrap_or(1);
let mut event_processed = false;
// --- CHANNEL RECEIVERS ---
@@ -162,6 +167,7 @@ pub async fn run_ui() -> Result<()> {
// --- ADDED: For live form autocomplete ---
match event_handler.autocomplete_result_receiver.try_recv() {
Ok(hits) => {
if let Some(form_state) = app_state.form_state_mut() {
if form_state.autocomplete_active {
form_state.autocomplete_suggestions = hits;
form_state.autocomplete_loading = false;
@@ -172,6 +178,7 @@ pub async fn run_ui() -> Result<()> {
}
event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len());
}
}
needs_redraw = true;
}
Err(mpsc::error::TryRecvError::Empty) => {}
@@ -180,19 +187,22 @@ pub async fn run_ui() -> Result<()> {
}
}
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;
let event_outcome_result = event_handler.handle_event(
let event_outcome_result = {
// We need to avoid borrowing app_state twice, so we'll need to modify the handle_event call
// For now, let's create a temporary approach
let mut temp_form_state = app_state.form_state_mut().unwrap().clone();
let result = event_handler.handle_event(
event,
&config,
&mut terminal,
&mut command_handler,
&mut form_state,
&mut temp_form_state,
&mut auth_state,
&mut login_state,
&mut register_state,
@@ -202,6 +212,14 @@ pub async fn run_ui() -> Result<()> {
&mut app_state,
).await;
// Update the app_state with any changes from temp_form_state
if let Some(form_state) = app_state.form_state_mut() {
*form_state = temp_form_state;
}
result
};
let mut should_exit = false;
match event_outcome_result {
Ok(outcome) => match outcome {
@@ -216,15 +234,21 @@ pub async fn run_ui() -> Result<()> {
}
EventOutcome::DataSaved(save_outcome, message) => {
event_handler.command_message = message;
// Clone form_state to avoid double borrow
let mut temp_form_state = app_state.form_state().unwrap().clone();
if let Err(e) = UiService::handle_save_outcome(
save_outcome,
&mut grpc_client,
&mut app_state,
&mut form_state,
&mut temp_form_state,
).await {
event_handler.command_message =
format!("Error handling save outcome: {}", e);
}
// Update app_state with changes
if let Some(form_state) = app_state.form_state_mut() {
*form_state = temp_form_state;
}
}
EventOutcome::ButtonSelected { .. } => {}
EventOutcome::TableSelected { path } => {
@@ -373,10 +397,14 @@ pub async fn run_ui() -> Result<()> {
)
.await
{
Ok(mut new_form_state) => {
Ok(new_form_state) => {
// Set the new form state and fetch count
app_state.set_form_state(new_form_state, &config);
if let Some(form_state) = app_state.form_state_mut() {
if let Err(e) = UiService::fetch_and_set_table_count(
&mut grpc_client,
&mut new_form_state,
form_state,
)
.await
{
@@ -385,10 +413,10 @@ pub async fn run_ui() -> Result<()> {
vec!["OK".to_string()],
DialogPurpose::LoginFailed,
);
} else if new_form_state.total_count > 0 {
} else if form_state.total_count > 0 {
if let Err(e) = UiService::load_table_data_by_position(
&mut grpc_client,
&mut new_form_state,
form_state,
)
.await
{
@@ -401,11 +429,11 @@ pub async fn run_ui() -> Result<()> {
app_state.hide_dialog();
}
} else {
new_form_state.reset_to_empty();
form_state.reset_to_empty();
app_state.hide_dialog();
}
}
form_state = new_form_state;
prev_view_profile_name = current_view_profile;
prev_view_table_name = current_view_table;
table_just_switched = true;
@@ -488,18 +516,22 @@ pub async fn run_ui() -> Result<()> {
}
}
let position_changed = form_state.current_position != position_before_event;
let current_position = app_state.form_state()
.map(|fs| fs.current_position)
.unwrap_or(1);
let position_changed = current_position != position_before_event;
let mut position_logic_needs_redraw = false;
if app_state.ui.show_form && !table_just_switched {
if position_changed && !event_handler.is_edit_mode {
position_logic_needs_redraw = true;
if let Some(form_state) = app_state.form_state_mut() {
if form_state.current_position > form_state.total_count {
form_state.reset_to_empty();
event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name);
} else {
match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await {
match UiService::load_table_data_by_position(&mut grpc_client, form_state).await {
Ok(load_message) => {
if event_handler.command_message.is_empty() || !load_message.starts_with("Error") {
event_handler.command_message = load_message;
@@ -519,8 +551,9 @@ pub async fn run_ui() -> Result<()> {
0
};
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
}
} else if !position_changed && !event_handler.is_edit_mode {
if let Some(form_state) = app_state.form_state_mut() {
let current_input_str = form_state.get_current_input();
let current_input_len = current_input_str.chars().count();
let max_cursor_pos = if current_input_len > 0 {
@@ -530,6 +563,7 @@ pub async fn run_ui() -> Result<()> {
};
form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos);
}
}
} else if app_state.ui.show_register {
if !event_handler.is_edit_mode {
let current_input = register_state.get_current_input();
@@ -587,10 +621,23 @@ pub async fn run_ui() -> Result<()> {
AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; }
}
// Temporarily work around borrow checker by extracting needed values
let current_dir = app_state.current_dir.clone();
// Since we can't borrow app_state both mutably and immutably,
// we'll need to either:
// 1. Modify render_ui to take just app_state and access form_state internally, OR
// 2. Extract the specific fields render_ui needs from app_state
// For now, using approach where we temporarily clone what we need
let form_state_clone = app_state.form_state().unwrap().clone();
terminal.draw(|f| {
// Use a mutable clone for rendering
let mut temp_form_state = form_state_clone.clone();
render_ui(
f,
&mut form_state,
&mut temp_form_state,
&mut auth_state,
&login_state,
&register_state,
@@ -603,10 +650,13 @@ pub async fn run_ui() -> Result<()> {
event_handler.command_mode,
&event_handler.command_message,
&event_handler.navigation_state,
&app_state.current_dir,
&current_dir,
current_fps,
&app_state,
);
// If render_ui modified the form_state, we'd need to sync it back
// But typically render functions don't modify state, just read it
}).context("Terminal draw call failed")?;
needs_redraw = false;
}