From 02c62213c3dd0c70298178ef58d99129c66cb2ef Mon Sep 17 00:00:00 2001 From: filipriec Date: Fri, 6 Jun 2025 23:44:29 +0200 Subject: [PATCH] making select from the find file to work, not yet working, needs more redesign in how select is working --- client/src/components/handlers/buffer_list.rs | 5 +- .../src/modes/general/command_navigation.rs | 12 +- client/src/modes/handlers/event.rs | 1 + client/src/ui/handlers/ui.rs | 501 +++++++++--------- 4 files changed, 268 insertions(+), 251 deletions(-) diff --git a/client/src/components/handlers/buffer_list.rs b/client/src/components/handlers/buffer_list.rs index a8283d6..fd814c5 100644 --- a/client/src/components/handlers/buffer_list.rs +++ b/client/src/components/handlers/buffer_list.rs @@ -18,7 +18,7 @@ pub fn render_buffer_list( area: Rect, theme: &Theme, buffer_state: &BufferState, - app_state: &AppState, // Add this parameter + app_state: &AppState, ) { // --- Style Definitions --- let active_style = Style::default() @@ -39,8 +39,7 @@ pub fn render_buffer_list( let mut spans = Vec::new(); let mut current_width = 0; - // TODO: Replace with actual table name from server response - let current_table_name = Some("2025_customer"); + let current_table_name = app_state.current_view_table_name.as_deref(); for (original_index, view) in buffer_state.history.iter().enumerate() { // Filter: Only process views matching the active layer diff --git a/client/src/modes/general/command_navigation.rs b/client/src/modes/general/command_navigation.rs index 25ad577..62e274d 100644 --- a/client/src/modes/general/command_navigation.rs +++ b/client/src/modes/general/command_navigation.rs @@ -340,12 +340,16 @@ pub async fn handle_command_navigation_event( } KeyCode::Enter => { if let Some(selected_value) = navigation_state.get_selected_value() { - let message = match navigation_state.navigation_type { - NavigationType::FindFile => format!("Selected file: {}", selected_value), - NavigationType::TableTree => format!("Selected table: {}", selected_value), + let outcome = match navigation_state.navigation_type { + NavigationType::FindFile => { + EventOutcome::Ok(format!("Selected file: {}", selected_value)) + } + NavigationType::TableTree => { + EventOutcome::TableSelected { path: selected_value } + } }; navigation_state.deactivate(); - Ok(EventOutcome::Ok(message)) + Ok(outcome) } else { // Enhanced Enter behavior for TableTree: if input is a valid partial path, try to navigate if navigation_state.navigation_type == NavigationType::TableTree && !navigation_state.input.is_empty() { diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index d0c6b78..29f631f 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -52,6 +52,7 @@ pub enum EventOutcome { Exit(String), DataSaved(SaveOutcome, String), ButtonSelected { context: UiContext, index: usize }, + TableSelected { path: String }, } impl EventOutcome { diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index cbac925..5b206c8 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -96,7 +96,7 @@ pub async fn run_ui() -> Result<()> { // Filter the columns obtained from the service let filtered_columns = filter_user_columns(initial_columns_from_service); // Use the correct variable - + let mut form_state = FormState::new( initial_profile.clone(), initial_table.clone(), @@ -132,208 +132,15 @@ pub async fn run_ui() -> Result<()> { let mut prev_view_table_name = app_state.current_view_table_name.clone(); loop { - if let Some(active_view) = buffer_state.get_active_view() { - app_state.ui.show_intro = false; - app_state.ui.show_login = false; - app_state.ui.show_register = false; - app_state.ui.show_admin = false; - app_state.ui.show_add_table = false; - app_state.ui.show_add_logic = false; - app_state.ui.show_form = false; - match active_view { - AppView::Intro => app_state.ui.show_intro = true, - AppView::Login => app_state.ui.show_login = true, - AppView::Register => app_state.ui.show_register = true, - AppView::Admin => { - info!("Active view is Admin, refreshing profile tree..."); - match grpc_client.get_profile_tree().await { - Ok(refreshed_tree) => { - app_state.profile_tree = refreshed_tree; - } - Err(e) => { - error!("Failed to refresh profile tree for Admin panel: {}", e); - event_handler.command_message = format!("Error refreshing admin data: {}", e); - } - } - app_state.ui.show_admin = true; - let profile_names = app_state.profile_tree.profiles.iter() - .map(|p| p.name.clone()) - .collect(); - admin_state.set_profiles(profile_names); - - if admin_state.current_focus == AdminFocus::default() || - !matches!(admin_state.current_focus, - AdminFocus::InsideProfilesList | - AdminFocus::Tables | AdminFocus::InsideTablesList | - AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) { - admin_state.current_focus = AdminFocus::ProfilesPane; - } - if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() { - admin_state.profile_list_state.select(Some(0)); - } - } - AppView::AddTable => app_state.ui.show_add_table = true, - AppView::AddLogic => app_state.ui.show_add_logic = true, - AppView::Form => app_state.ui.show_form = true, - AppView::Scratch => {} - } - } - - // Handle table change for FormView - if app_state.ui.show_form { - let current_view_profile = app_state.current_view_profile_name.clone(); - let current_view_table = app_state.current_view_table_name.clone(); - - if prev_view_profile_name != current_view_profile || prev_view_table_name != current_view_table { - if let (Some(prof_name), Some(tbl_name)) = (current_view_profile.as_ref(), current_view_table.as_ref()) { - app_state.show_loading_dialog("Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name)); - needs_redraw = true; - - match grpc_client.get_table_structure(prof_name.clone(), tbl_name.clone()).await { - Ok(structure_response) => { - let new_columns: Vec = structure_response.columns.iter().map(|c| c.name.clone()).collect(); - form_state = FormState::new(prof_name.clone(), tbl_name.clone(), new_columns); - - if let Err(e) = UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state).await { - app_state.update_dialog_content(&format!("Error fetching count: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); - } else { - if form_state.total_count > 0 { - if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await { - app_state.update_dialog_content(&format!("Error loading data: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); - } else { - app_state.hide_dialog(); - } - } else { - form_state.reset_to_empty(); - app_state.hide_dialog(); - } - } - } - Err(e) => { - app_state.update_dialog_content(&format!("Error fetching table structure: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); - app_state.current_view_profile_name = prev_view_profile_name.clone(); - app_state.current_view_table_name = prev_view_table_name.clone(); - } - } - } - prev_view_profile_name = current_view_profile; - prev_view_table_name = current_view_table; - needs_redraw = true; - } - } - - if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() { - if app_state.ui.show_add_logic { - if admin_state.add_logic_state.profile_name == profile_name && - admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) { - info!("Fetching table structure for {}.{}", profile_name, table_name); - let fetch_message = UiService::initialize_add_logic_table_data( - &mut grpc_client, - &mut admin_state.add_logic_state, - &app_state.profile_tree, - ).await.unwrap_or_else(|e| { - error!("Error initializing add_logic_table_data: {}", e); - format!("Error fetching table structure: {}", e) - }); - - if !fetch_message.contains("Error") && !fetch_message.contains("Warning") { - info!("{}", fetch_message); - } else { - event_handler.command_message = fetch_message; - } - needs_redraw = true; - } else { - error!( - "Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}", - profile_name, table_name, - admin_state.add_logic_state.profile_name, - admin_state.add_logic_state.selected_table_name - ); - } - } else { - warn!( - "Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.", - profile_name, table_name - ); - } - } - - if needs_redraw { - terminal.draw(|f| { - render_ui( - f, - &mut form_state, - &mut auth_state, - &login_state, - ®ister_state, - &intro_state, - &mut admin_state, - &buffer_state, - &theme, - event_handler.is_edit_mode, - &event_handler.highlight_state, - &event_handler.command_input, - event_handler.command_mode, - &event_handler.command_message, - &event_handler.navigation_state, - &app_state.current_dir, - current_fps, - &app_state, - ); - }).context("Terminal draw call failed")?; - needs_redraw = false; - } - - if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() { - if app_state.ui.show_add_logic { - let profile_name = admin_state.add_logic_state.profile_name.clone(); - - info!("Fetching columns for table selection: {}.{}", profile_name, table_name); - match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await { - Ok(columns) => { - admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone()); - info!("Loaded {} columns for table '{}'", columns.len(), table_name); - event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name); - } - Err(e) => { - error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e); - admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None; - admin_state.add_logic_state.deactivate_script_editor_autocomplete(); - event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e); - } - } - needs_redraw = true; - } - } - - let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state); - match current_mode { - AppMode::Edit => { terminal.show_cursor()?; } - AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; } - AppMode::ReadOnly => { - if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; } - else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; } - terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?; - } - AppMode::General => { - if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; } - else { terminal.hide_cursor()?; } - } - AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; } - } - + // =================================================================== + // PROCESS EVENTS & UPDATE STATE + // =================================================================== let position_before_event = form_state.current_position; - - if app_state.ui.dialog.is_loading { - needs_redraw = true; - } - - let mut event_outcome_result = Ok(EventOutcome::Ok(String::new())); let mut event_processed = false; 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; - event_outcome_result = event_handler.handle_event( + let event_outcome_result = event_handler.handle_event( event, &config, &mut terminal, @@ -348,12 +155,57 @@ pub async fn run_ui() -> Result<()> { &mut buffer_state, &mut app_state, ).await; + + // --- Handle the outcome of the event --- + let mut should_exit = false; + match event_outcome_result { + Ok(outcome) => match outcome { + EventOutcome::Ok(message) => { + if !message.is_empty() { + event_handler.command_message = message; + } + } + EventOutcome::Exit(message) => { + event_handler.command_message = message; + should_exit = true; + } + EventOutcome::DataSaved(save_outcome, message) => { + event_handler.command_message = message; + if let Err(e) = UiService::handle_save_outcome( + save_outcome, + &mut grpc_client, + &mut app_state, + &mut form_state, + ).await { + event_handler.command_message = + format!("Error handling save outcome: {}", e); + } + } + EventOutcome::ButtonSelected { .. } => {} + EventOutcome::TableSelected { path } => { + let parts: Vec<&str> = path.split('/').collect(); + if parts.len() == 2 { + let profile_name = parts[0].to_string(); + let table_name = parts[1].to_string(); + + app_state.set_current_view_table(profile_name, table_name); + buffer_state.update_history(AppView::Form); + event_handler.command_message = format!("Loading table: {}", path); + } else { + event_handler.command_message = format!("Invalid table path: {}", path); + } + } + }, + Err(e) => { + event_handler.command_message = format!("Error: {}", e); + } + } + if should_exit { + return Ok(()); + } } - if event_processed { - needs_redraw = true; - } - + // --- Handle async results from other tasks --- match login_result_receiver.try_recv() { Ok(result) => { if login::handle_login_result(result, &mut app_state, &mut auth_state, &mut login_state) { @@ -403,62 +255,182 @@ pub async fn run_ui() -> Result<()> { } } - let mut should_exit = false; - match event_outcome_result { - Ok(outcome) => match outcome { - EventOutcome::Ok(_message) => {} - EventOutcome::Exit(message) => { - event_handler.command_message = message; - should_exit = true; - } - EventOutcome::DataSaved(save_outcome, message) => { - event_handler.command_message = message; - if let Err(e) = UiService::handle_save_outcome( - save_outcome, - &mut grpc_client, - &mut app_state, - &mut form_state, - ) - .await - { - event_handler.command_message = - format!("Error handling save outcome: {}", e); + // =================================================================== + // STATE-DEPENDENT LOGIC (NOW RUNS AFTER STATE IS UPDATED) + // =================================================================== + + // --- Determine which view is active --- + if let Some(active_view) = buffer_state.get_active_view() { + app_state.ui.show_intro = false; + app_state.ui.show_login = false; + app_state.ui.show_register = false; + app_state.ui.show_admin = false; + app_state.ui.show_add_table = false; + app_state.ui.show_add_logic = false; + app_state.ui.show_form = false; + match active_view { + AppView::Intro => app_state.ui.show_intro = true, + AppView::Login => app_state.ui.show_login = true, + AppView::Register => app_state.ui.show_register = true, + AppView::Admin => { + info!("Active view is Admin, refreshing profile tree..."); + match grpc_client.get_profile_tree().await { + Ok(refreshed_tree) => { + app_state.profile_tree = refreshed_tree; + } + Err(e) => { + error!("Failed to refresh profile tree for Admin panel: {}", e); + event_handler.command_message = format!("Error refreshing admin data: {}", e); + } + } + app_state.ui.show_admin = true; + let profile_names = app_state.profile_tree.profiles.iter() + .map(|p| p.name.clone()) + .collect(); + admin_state.set_profiles(profile_names); + + if admin_state.current_focus == AdminFocus::default() || + !matches!(admin_state.current_focus, + AdminFocus::InsideProfilesList | + AdminFocus::Tables | AdminFocus::InsideTablesList | + AdminFocus::Button1 | AdminFocus::Button2 | AdminFocus::Button3) { + admin_state.current_focus = AdminFocus::ProfilesPane; + } + if admin_state.profile_list_state.selected().is_none() && !app_state.profile_tree.profiles.is_empty() { + admin_state.profile_list_state.select(Some(0)); } } - EventOutcome::ButtonSelected { context: _, index: _ } => {} - }, - Err(e) => { - event_handler.command_message = format!("Error: {}", e); + AppView::AddTable => app_state.ui.show_add_table = true, + AppView::AddLogic => app_state.ui.show_add_logic = true, + AppView::Form => app_state.ui.show_form = true, + AppView::Scratch => {} } } -// --- MODIFIED: Position Change Handling (operates on form_state) --- + // --- Handle table change for FormView --- + if app_state.ui.show_form { + let current_view_profile = app_state.current_view_profile_name.clone(); + let current_view_table = app_state.current_view_table_name.clone(); + + if prev_view_profile_name != current_view_profile || prev_view_table_name != current_view_table { + if let (Some(prof_name), Some(tbl_name)) = (current_view_profile.as_ref(), current_view_table.as_ref()) { + app_state.show_loading_dialog("Loading Table", &format!("Fetching data for {}.{}...", prof_name, tbl_name)); + needs_redraw = true; + + match grpc_client.get_table_structure(prof_name.clone(), tbl_name.clone()).await { + Ok(structure_response) => { + let new_columns: Vec = structure_response.columns.iter().map(|c| c.name.clone()).collect(); + form_state = FormState::new(prof_name.clone(), tbl_name.clone(), new_columns); + + if let Err(e) = UiService::fetch_and_set_table_count(&mut grpc_client, &mut form_state).await { + app_state.update_dialog_content(&format!("Error fetching count: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); + } else { + if form_state.total_count > 0 { + if let Err(e) = UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await { + app_state.update_dialog_content(&format!("Error loading data: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); + } else { + app_state.hide_dialog(); + } + } else { + form_state.reset_to_empty(); + app_state.hide_dialog(); + } + } + } + Err(e) => { + app_state.update_dialog_content(&format!("Error fetching table structure: {}", e), vec!["OK".to_string()], DialogPurpose::LoginFailed); + app_state.current_view_profile_name = prev_view_profile_name.clone(); + app_state.current_view_table_name = prev_view_table_name.clone(); + } + } + } + prev_view_profile_name = current_view_profile; + prev_view_table_name = current_view_table; + needs_redraw = true; + } + } + + // --- Handle pending async fetches --- + if let Some((profile_name, table_name)) = app_state.pending_table_structure_fetch.take() { + if app_state.ui.show_add_logic { + if admin_state.add_logic_state.profile_name == profile_name && + admin_state.add_logic_state.selected_table_name.as_deref() == Some(table_name.as_str()) { + info!("Fetching table structure for {}.{}", profile_name, table_name); + let fetch_message = UiService::initialize_add_logic_table_data( + &mut grpc_client, + &mut admin_state.add_logic_state, + &app_state.profile_tree, + ).await.unwrap_or_else(|e| { + error!("Error initializing add_logic_table_data: {}", e); + format!("Error fetching table structure: {}", e) + }); + + if !fetch_message.contains("Error") && !fetch_message.contains("Warning") { + info!("{}", fetch_message); + } else { + event_handler.command_message = fetch_message; + } + needs_redraw = true; + } else { + error!( + "Mismatch in pending_table_structure_fetch: app_state wants {}.{}, but add_logic_state is for {}.{:?}", + profile_name, table_name, + admin_state.add_logic_state.profile_name, + admin_state.add_logic_state.selected_table_name + ); + } + } else { + warn!( + "Pending table structure fetch for {}.{} but AddLogic view is not active. Fetch ignored.", + profile_name, table_name + ); + } + } + + if let Some(table_name) = admin_state.add_logic_state.script_editor_awaiting_column_autocomplete.clone() { + if app_state.ui.show_add_logic { + let profile_name = admin_state.add_logic_state.profile_name.clone(); + + info!("Fetching columns for table selection: {}.{}", profile_name, table_name); + match UiService::fetch_columns_for_table(&mut grpc_client, &profile_name, &table_name).await { + Ok(columns) => { + admin_state.add_logic_state.set_columns_for_table_autocomplete(columns.clone()); + info!("Loaded {} columns for table '{}'", columns.len(), table_name); + event_handler.command_message = format!("Columns for '{}' loaded. Select a column.", table_name); + } + Err(e) => { + error!("Failed to fetch columns for {}.{}: {}", profile_name, table_name, e); + admin_state.add_logic_state.script_editor_awaiting_column_autocomplete = None; + admin_state.add_logic_state.deactivate_script_editor_autocomplete(); + event_handler.command_message = format!("Error loading columns for '{}': {}", table_name, e); + } + } + needs_redraw = true; + } + } + + // --- Handle cursor updates based on position changes --- let position_changed = form_state.current_position != position_before_event; let mut position_logic_needs_redraw = false; - if app_state.ui.show_form { // Only if the form is active + if app_state.ui.show_form { if position_changed && !event_handler.is_edit_mode { - // This part is okay: update cursor for the current field BEFORE loading new data let current_input_before_load = form_state.get_current_input(); let max_cursor_pos_before_load = if !current_input_before_load.is_empty() { current_input_before_load.chars().count() } else { 0 }; form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_before_load); position_logic_needs_redraw = true; - // Validate new form_state.current_position if form_state.total_count > 0 && form_state.current_position > form_state.total_count + 1 { - form_state.current_position = form_state.total_count + 1; // Cap at new entry + form_state.current_position = form_state.total_count + 1; } else if form_state.total_count == 0 && form_state.current_position > 1 { - form_state.current_position = 1; // Cap at new entry for empty table + form_state.current_position = 1; } if form_state.current_position == 0 && form_state.total_count > 0 { - form_state.current_position = 1; // Don't allow 0 if there are records + form_state.current_position = 1; } - - // Load data for the new position OR reset for new entry if (form_state.total_count > 0 && form_state.current_position <= form_state.total_count && form_state.current_position > 0) { - // It's an existing record position match UiService::load_table_data_by_position(&mut grpc_client, &mut form_state).await { Ok(load_message) => { if event_handler.command_message.is_empty() || !load_message.starts_with("Error") { @@ -467,16 +439,13 @@ pub async fn run_ui() -> Result<()> { } Err(e) => { event_handler.command_message = format!("Error loading data: {}", e); - // Consider what to do with form_state here - maybe revert position or clear form } } } else { - // Position indicates a new entry (or table is empty and position is 1) - form_state.reset_to_empty(); // This sets id=0, clears values, and sets current_position correctly + form_state.reset_to_empty(); event_handler.command_message = format!("New entry for {}.{}", form_state.profile_name, form_state.table_name); } - // NOW, after data is loaded or form is reset, get the current input string and its length let current_input_after_load_str = form_state.get_current_input(); let current_input_len_after_load = current_input_after_load_str.chars().count(); @@ -490,11 +459,9 @@ pub async fn run_ui() -> Result<()> { form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(current_input_len_after_load); } else { form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos_for_readonly_after_load); - // The check for empty string is implicitly handled by max_cursor_pos_for_readonly_after_load being 0 } } else if !position_changed && !event_handler.is_edit_mode && app_state.ui.show_form { - // Update cursor if not editing and position didn't change (e.g. arrow keys within field) 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 { @@ -522,10 +489,56 @@ pub async fn run_ui() -> Result<()> { needs_redraw = true; } - if should_exit { - return Ok(()); + if app_state.ui.dialog.is_loading { + needs_redraw = true; } + // =================================================================== + // STEP 3: DRAW THE UI + // =================================================================== + if event_processed || needs_redraw || position_changed { + let current_mode = ModeManager::derive_mode(&app_state, &event_handler, &admin_state); + match current_mode { + AppMode::Edit => { terminal.show_cursor()?; } + AppMode::Highlight => { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; terminal.show_cursor()?; } + AppMode::ReadOnly => { + if !app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; } + else { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; } + terminal.show_cursor().context("Failed to show cursor in ReadOnly mode")?; + } + AppMode::General => { + if app_state.ui.focus_outside_canvas { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor()?; } + else { terminal.hide_cursor()?; } + } + AppMode::Command => { terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; terminal.show_cursor().context("Failed to show cursor in Command mode")?; } + } + + terminal.draw(|f| { + render_ui( + f, + &mut form_state, + &mut auth_state, + &login_state, + ®ister_state, + &intro_state, + &mut admin_state, + &buffer_state, + &theme, + event_handler.is_edit_mode, + &event_handler.highlight_state, + &event_handler.command_input, + event_handler.command_mode, + &event_handler.command_message, + &event_handler.navigation_state, + &app_state.current_dir, + current_fps, + &app_state, + ); + }).context("Terminal draw call failed")?; + needs_redraw = false; + } + + // --- FPS calculation --- let now = Instant::now(); let frame_duration = now.duration_since(last_frame_time); last_frame_time = now;