we compiled but buffer doesnt work

This commit is contained in:
filipriec
2025-08-29 18:11:27 +02:00
parent 58f109ca91
commit 16dd460469
13 changed files with 256 additions and 166 deletions

View File

@@ -6,6 +6,7 @@ use crate::bottom_panel::find_file_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::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::pages::routing::Router;
/// Calculate the layout constraints for the bottom panel (status line + command line/palette). /// Calculate the layout constraints for the bottom panel (status line + command line/palette).
pub fn bottom_panel_constraints( pub fn bottom_panel_constraints(
@@ -48,6 +49,7 @@ pub fn render_bottom_panel(
theme: &Theme, theme: &Theme,
current_fps: f64, current_fps: f64,
app_state: &AppState, app_state: &AppState,
router: &Router,
navigation_state: &NavigationState, navigation_state: &NavigationState,
event_handler_command_input: &str, event_handler_command_input: &str,
event_handler_command_mode_active: bool, event_handler_command_mode_active: bool,
@@ -75,6 +77,7 @@ pub fn render_bottom_panel(
theme, theme,
current_fps, current_fps,
app_state, app_state,
router,
); );
// --- Render command line or palette --- // --- Render command line or palette ---

View File

@@ -8,6 +8,8 @@ use ratatui::{
widgets::{Paragraph, Wrap}, widgets::{Paragraph, Wrap},
Frame, Frame,
}; };
use crate::pages::routing::Page;
use crate::pages::routing::Router;
use std::path::Path; use std::path::Path;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@@ -18,6 +20,7 @@ pub fn render_status_line(
theme: &Theme, theme: &Theme,
current_fps: f64, current_fps: f64,
app_state: &AppState, app_state: &AppState,
router: &Router,
) { ) {
#[cfg(feature = "ui-debug")] #[cfg(feature = "ui-debug")]
{ {
@@ -47,12 +50,16 @@ pub fn render_status_line(
// --- The normal status line rendering logic (unchanged) --- // --- The normal status line rendering logic (unchanged) ---
let program_info = format!("komp_ac v{}", env!("CARGO_PKG_VERSION")); let program_info = format!("komp_ac v{}", env!("CARGO_PKG_VERSION"));
let mode_text = if let Some(editor) = &app_state.form_editor { let mode_text = if let Page::Form(path) = &router.current {
match editor.mode() { if let Some(editor) = app_state.editor_for_path_ref(path) {
canvas::AppMode::Edit => "[EDIT]", match editor.mode() {
canvas::AppMode::ReadOnly => "[READ-ONLY]", canvas::AppMode::Edit => "[EDIT]",
canvas::AppMode::Highlight => "[VISUAL]", canvas::AppMode::ReadOnly => "[READ-ONLY]",
_ => "", canvas::AppMode::Highlight => "[VISUAL]",
_ => "",
}
} else {
""
} }
} else { } else {
"" // No canvas active "" // No canvas active

View File

@@ -98,19 +98,27 @@ async fn process_command(
} }
} }
"save" => { "save" => {
let outcome = save(app_state, grpc_client).await?; if let Page::Form(path) = &router.current {
let message = match outcome { let outcome = save(app_state, path, grpc_client).await?;
SaveOutcome::CreatedNew(_) => "New entry created".to_string(), let message = match outcome {
SaveOutcome::UpdatedExisting => "Entry updated".to_string(), SaveOutcome::CreatedNew(_) => "New entry created".to_string(),
SaveOutcome::NoChange => "No changes to save".to_string(), SaveOutcome::UpdatedExisting => "Entry updated".to_string(),
}; SaveOutcome::NoChange => "No changes to save".to_string(),
command_input.clear(); };
Ok(EventOutcome::DataSaved(outcome, message)) command_input.clear();
Ok(EventOutcome::DataSaved(outcome, message))
} else {
Ok(EventOutcome::Ok("Not in a form page".to_string()))
}
} }
"revert" => { "revert" => {
let message = revert(app_state, grpc_client).await?; if let Page::Form(path) = &router.current {
command_input.clear(); let message = revert(app_state, path, grpc_client).await?;
Ok(EventOutcome::Ok(message)) command_input.clear();
Ok(EventOutcome::Ok(message))
} else {
Ok(EventOutcome::Ok("Not in a form page".to_string()))
}
} }
_ => { _ => {
let message = format!("Unhandled action: {}", action); let message = format!("Unhandled action: {}", action);

View File

@@ -36,7 +36,10 @@ impl CommandHandler {
let has_unsaved = match &router.current { let has_unsaved = match &router.current {
Page::Login(page) => page.state.has_unsaved_changes(), Page::Login(page) => page.state.has_unsaved_changes(),
Page::Register(state) => state.has_unsaved_changes(), Page::Register(state) => state.has_unsaved_changes(),
Page::Form(fs) => fs.has_unsaved_changes, Page::Form(path) => app_state
.form_state_for_path_ref(path)
.map(|fs| fs.has_unsaved_changes())
.unwrap_or(false),
_ => false, _ => false,
}; };

View File

@@ -45,14 +45,18 @@ pub async fn handle_navigation_event(
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"next_field" => { "next_field" => {
if let Some(fs) = app_state.form_state_mut() { if let Page::Form(path) = &router.current {
next_field(fs); if let Some(fs) = app_state.form_state_for_path(path) {
next_field(fs);
}
} }
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }
"prev_field" => { "prev_field" => {
if let Some(fs) = app_state.form_state_mut() { if let Page::Form(path) = &router.current {
prev_field(fs); if let Some(fs) = app_state.form_state_for_path(path) {
prev_field(fs);
}
} }
return Ok(EventOutcome::Ok(String::new())); return Ok(EventOutcome::Ok(String::new()));
} }

View File

@@ -128,69 +128,84 @@ impl EventHandler {
} }
// Helper functions - replace the removed event_helper functions // Helper functions - replace the removed event_helper functions
fn get_current_field_for_state( fn get_current_field_for_state(&self, router: &Router, app_state: &AppState) -> usize {
router: &Router,
) -> usize {
match &router.current { match &router.current {
Page::Login(state) => state.current_field(), Page::Login(state) => state.current_field(),
Page::Register(state) => state.current_field(), Page::Register(state) => state.current_field(),
Page::Form(state) => state.current_field(), Page::Form(path) => app_state
.editor_for_path_ref(path)
.map(|e| e.data_provider().current_field())
.unwrap_or(0),
_ => 0, _ => 0,
} }
} }
fn get_current_cursor_pos_for_state( fn get_current_cursor_pos_for_state(&self, router: &Router, app_state: &AppState) -> usize {
router: &Router,
) -> usize {
match &router.current { match &router.current {
Page::Login(state) => state.current_cursor_pos(), Page::Login(state) => state.current_cursor_pos(),
Page::Register(state) => state.current_cursor_pos(), Page::Register(state) => state.current_cursor_pos(),
Page::Form(state) => state.current_cursor_pos(), Page::Form(path) => app_state
.form_state_for_path_ref(path)
.map(|fs| fs.current_cursor_pos())
.unwrap_or(0),
_ => 0, _ => 0,
} }
} }
fn get_has_unsaved_changes_for_state( fn get_has_unsaved_changes_for_state(&self, router: &Router, app_state: &AppState) -> bool {
router: &Router,
) -> bool {
match &router.current { match &router.current {
Page::Login(state) => state.has_unsaved_changes(), Page::Login(state) => state.has_unsaved_changes(),
Page::Register(state) => state.has_unsaved_changes(), Page::Register(state) => state.has_unsaved_changes(),
Page::Form(state) => state.has_unsaved_changes(), Page::Form(path) => app_state
.form_state_for_path_ref(path)
.map(|fs| fs.has_unsaved_changes())
.unwrap_or(false),
_ => false, _ => false,
} }
} }
fn get_current_input_for_state<'a>( fn get_current_input_for_state<'a>(
&'a self,
router: &'a Router, router: &'a Router,
app_state: &'a AppState,
) -> &'a str { ) -> &'a str {
match &router.current { match &router.current {
Page::Login(state) => state.get_current_input(), Page::Login(state) => state.get_current_input(),
Page::Register(state) => state.get_current_input(), Page::Register(state) => state.get_current_input(),
Page::Form(state) => state.get_current_input(), Page::Form(path) => app_state
.form_state_for_path_ref(path)
.map(|fs| fs.get_current_input())
.unwrap_or(""),
_ => "", _ => "",
} }
} }
fn set_current_cursor_pos_for_state( fn set_current_cursor_pos_for_state(
&mut self,
router: &mut Router, router: &mut Router,
app_state: &mut AppState,
pos: usize, pos: usize,
) { ) {
match &mut router.current { match &mut router.current {
Page::Login(state) => state.set_current_cursor_pos(pos), Page::Login(state) => state.set_current_cursor_pos(pos),
Page::Register(state) => state.set_current_cursor_pos(pos), Page::Register(state) => state.set_current_cursor_pos(pos),
Page::Form(state) => state.set_current_cursor_pos(pos), Page::Form(path) => {
if let Some(fs) = app_state.form_state_for_path(path) {
fs.set_current_cursor_pos(pos);
}
}
_ => {}, _ => {},
} }
} }
fn get_cursor_pos_for_mixed_state( fn get_cursor_pos_for_mixed_state(&self, router: &Router, app_state: &AppState) -> usize {
router: &Router,
) -> usize {
match &router.current { match &router.current {
Page::Login(state) => state.current_cursor_pos(), Page::Login(state) => state.current_cursor_pos(),
Page::Register(state) => state.current_cursor_pos(), Page::Register(state) => state.current_cursor_pos(),
Page::Form(state) => state.current_cursor_pos(), Page::Form(path) => app_state
.form_state_for_path_ref(path)
.map(|fs| fs.current_cursor_pos())
.unwrap_or(0),
_ => 0, _ => 0,
} }
} }
@@ -254,7 +269,7 @@ impl EventHandler {
Page::Admin(_) => AppView::Admin, Page::Admin(_) => AppView::Admin,
Page::AddLogic(_) => AppView::AddLogic, Page::AddLogic(_) => AppView::AddLogic,
Page::AddTable(_) => AppView::AddTable, Page::AddTable(_) => AppView::AddTable,
Page::Form(_) => AppView::Form, Page::Form(path) => AppView::Form(path.clone()),
}; };
buffer_state.update_history(current_view); buffer_state.update_history(current_view);
@@ -678,8 +693,10 @@ impl EventHandler {
self.command_message.clear(); self.command_message.clear();
self.command_mode = false; self.command_mode = false;
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
if let Some(editor) = &mut app_state.form_editor { if let Page::Form(path) = &router.current {
editor.set_mode(CanvasMode::ReadOnly); if let Some(editor) = app_state.editor_for_path(path) {
editor.set_mode(CanvasMode::ReadOnly);
}
} }
return Ok(EventOutcome::Ok( return Ok(EventOutcome::Ok(
"Exited command mode".to_string(), "Exited command mode".to_string(),
@@ -687,11 +704,18 @@ impl EventHandler {
} }
if config.is_command_execute(key_code, modifiers) { if config.is_command_execute(key_code, modifiers) {
let (mut current_position, total_count) = if let Page::Form(fs) = &router.current { let (mut current_position, total_count) =
(fs.current_position, fs.total_count) if let Page::Form(path) = &router.current {
} else { if let Some(fs) =
(1, 0) app_state.form_state_for_path_ref(path)
}; {
(fs.current_position, fs.total_count)
} else {
(1, 0)
}
} else {
(1, 0)
};
let outcome = command_mode::handle_command_event( let outcome = command_mode::handle_command_event(
key_event, key_event,
@@ -706,8 +730,10 @@ impl EventHandler {
&mut current_position, &mut current_position,
total_count, total_count,
).await?; ).await?;
if let Page::Form(fs) = &mut router.current { if let Page::Form(path) = &router.current {
fs.current_position = current_position; if let Some(fs) = app_state.form_state_for_path(path) {
fs.current_position = current_position;
}
} }
self.command_mode = false; self.command_mode = false;
self.key_sequence_tracker.reset(); self.key_sequence_tracker.reset();
@@ -834,9 +860,10 @@ impl EventHandler {
.await?; .await?;
Ok(EventOutcome::Ok(message)) Ok(EventOutcome::Ok(message))
} else { } else {
let save_outcome = if let Page::Form(_) = &router.current { let save_outcome = if let Page::Form(path) = &router.current {
save( save(
app_state, app_state,
path,
&mut self.grpc_client, &mut self.grpc_client,
) )
.await? .await?
@@ -852,8 +879,10 @@ impl EventHandler {
} }
} }
"force_quit" => { "force_quit" => {
if let Some(editor) = &mut app_state.form_editor { if let Page::Form(path) = &router.current {
editor.cleanup_cursor()?; if let Some(editor) = app_state.editor_for_path(path) {
editor.cleanup_cursor()?;
}
} }
terminal.cleanup()?; terminal.cleanup()?;
Ok(EventOutcome::Exit( Ok(EventOutcome::Exit(
@@ -870,18 +899,21 @@ impl EventHandler {
) )
.await? .await?
} else { } else {
let save_outcome = save( let save_outcome = if let Page::Form(path) = &router.current {
app_state, save(app_state, path, &mut self.grpc_client).await?
&mut self.grpc_client, } else {
).await?; SaveOutcome::NoChange
};
match save_outcome { match save_outcome {
SaveOutcome::NoChange => "No changes to save.".to_string(), SaveOutcome::NoChange => "No changes to save.".to_string(),
SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), SaveOutcome::UpdatedExisting => "Entry updated.".to_string(),
SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), SaveOutcome::CreatedNew(_) => "New entry created.".to_string(),
} }
}; };
if let Some(editor) = &mut app_state.form_editor { if let Page::Form(path) = &router.current {
editor.cleanup_cursor()?; if let Some(editor) = app_state.editor_for_path(path) {
editor.cleanup_cursor()?;
}
} }
terminal.cleanup()?; terminal.cleanup()?;
Ok(EventOutcome::Exit(format!( Ok(EventOutcome::Exit(format!(
@@ -899,12 +931,8 @@ impl EventHandler {
) )
.await .await
} else { } else {
if let Page::Form(_) = &router.current { if let Page::Form(path) = &router.current {
revert( revert(app_state, path, &mut self.grpc_client).await?
app_state,
&mut self.grpc_client,
)
.await?
} else { } else {
"Nothing to revert".to_string() "Nothing to revert".to_string()
} }

View File

@@ -15,9 +15,10 @@ pub enum SaveOutcome {
pub async fn save( pub async fn save(
app_state: &mut AppState, app_state: &mut AppState,
path: &str,
grpc_client: &mut GrpcClient, grpc_client: &mut GrpcClient,
) -> Result<SaveOutcome> { ) -> Result<SaveOutcome> {
if let Some(fs) = app_state.active_form_state_mut(buffer_state) { if let Some(fs) = app_state.form_state_for_path(path) {
if !fs.has_unsaved_changes { if !fs.has_unsaved_changes {
return Ok(SaveOutcome::NoChange); return Ok(SaveOutcome::NoChange);
} }
@@ -62,7 +63,7 @@ pub async fn save(
.context("Failed to post new table data")?; .context("Failed to post new table data")?;
if response.success { if response.success {
if let Some(fs) = app_state.active_form_state_mut(buffer_state) { if let Some(fs) = app_state.form_state_for_path(path) {
fs.id = response.inserted_id; fs.id = response.inserted_id;
fs.total_count += 1; fs.total_count += 1;
fs.current_position = fs.total_count; fs.current_position = fs.total_count;
@@ -84,7 +85,7 @@ pub async fn save(
.context("Failed to put (update) table data")?; .context("Failed to put (update) table data")?;
if response.success { if response.success {
if let Some(fs) = app_state.active_form_state_mut(buffer_state) { if let Some(fs) = app_state.form_state_for_path(path) {
fs.has_unsaved_changes = false; fs.has_unsaved_changes = false;
} }
SaveOutcome::UpdatedExisting SaveOutcome::UpdatedExisting
@@ -101,9 +102,10 @@ pub async fn save(
pub async fn revert( pub async fn revert(
app_state: &mut AppState, app_state: &mut AppState,
path: &str,
grpc_client: &mut GrpcClient, grpc_client: &mut GrpcClient,
) -> Result<String> { ) -> Result<String> {
if let Some(fs) = app_state.active_form_state_mut(buffer_state) { if let Some(fs) = app_state.form_state_for_path(path) {
if fs.id == 0 if fs.id == 0
|| (fs.total_count > 0 && fs.current_position > fs.total_count) || (fs.total_count > 0 && fs.current_position > fs.total_count)
|| (fs.total_count == 0 && fs.current_position == 1) || (fs.total_count == 0 && fs.current_position == 1)

View File

@@ -1,22 +1,20 @@
// src/pages/forms/ui.rs // src/pages/forms/ui.rs
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::app::state::AppState;
use ratatui::{ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect}, layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
style::Style, style::Style,
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
Frame, Frame,
}; };
use crate::pages::forms::FormState;
use canvas::{ use canvas::{
render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, render_canvas, render_suggestions_dropdown, DefaultCanvasTheme, FormEditor,
}; };
use crate::pages::forms::FormState;
pub fn render_form_page( pub fn render_form_page(
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
app_state: &AppState, editor: &FormEditor<FormState>,
form_state: &FormState, // not needed directly anymore, editor holds it
table_name: &str, table_name: &str,
theme: &Theme, theme: &Theme,
total_count: u64, total_count: u64,
@@ -61,18 +59,14 @@ pub fn render_form_page(
f.render_widget(count_para, main_layout[0]); f.render_widget(count_para, main_layout[0]);
// --- FORM RENDERING (Using persistent FormEditor) --- // --- FORM RENDERING (Using persistent FormEditor) ---
if let Some(AppView::Form(path)) = buffer_state.get_active_view() { let active_field_rect = render_canvas(f, main_layout[1], editor, theme);
if let Some(editor) = app_state.form_editor.get(path) { if let Some(active_rect) = active_field_rect {
let active_field_rect = render_canvas(f, main_layout[1], editor, theme); render_suggestions_dropdown(
if let Some(active_rect) = active_field_rect { f,
render_suggestions_dropdown( main_layout[1],
f, active_rect,
main_layout[1], &DefaultCanvasTheme,
active_rect, editor,
&DefaultCanvasTheme, );
editor,
);
}
}
} }
} }

View File

@@ -13,7 +13,7 @@ pub fn handle_intro_selection(
index: usize, index: usize,
) { ) {
let target_view = match index { let target_view = match index {
0 => AppView::Form, 0 => AppView::Form("".to_string()), // or better: pick a real path
1 => AppView::Admin, 1 => AppView::Admin,
2 => AppView::Login, 2 => AppView::Login,
3 => AppView::Register, 3 => AppView::Register,

View File

@@ -34,9 +34,16 @@ pub async fn handle_search_palette_event(
// Step 2: Process outside the borrow // Step 2: Process outside the borrow
if let Some((id, content_json)) = maybe_data { if let Some((id, content_json)) = maybe_data {
if let Ok(data) = serde_json::from_str::<HashMap<String, String>>(&content_json) { if let Ok(data) = serde_json::from_str::<HashMap<String, String>>(&content_json) {
if let Some(fs) = app_state.form_state_mut() { // Use current view path to access the active form
let detached_pos = fs.total_count + 2; if let (Some(profile), Some(table)) = (
fs.update_from_response(&data, detached_pos); app_state.current_view_profile_name.clone(),
app_state.current_view_table_name.clone(),
) {
let path = format!("{}/{}", profile, table);
if let Some(fs) = app_state.form_state_for_path(&path) {
let detached_pos = fs.total_count + 2;
fs.update_from_response(&data, detached_pos);
}
} }
should_close = true; should_close = true;
outcome_message = Some(format!("Loaded record ID {}", id)); outcome_message = Some(format!("Loaded record ID {}", id));

View File

@@ -99,16 +99,40 @@ impl AppState {
self.current_view_table_name = Some(table_name); self.current_view_table_name = Some(table_name);
} }
/// Returns true if the current view's editor is in Edit mode.
/// Uses current_view_profile_name/current_view_table_name to build the path.
pub fn is_canvas_edit_mode(&self) -> bool { pub fn is_canvas_edit_mode(&self) -> bool {
matches!(self.form_editor.as_ref().map(|e| e.mode()), Some(canvas::AppMode::Edit)) if let (Some(profile), Some(table)) =
(self.current_view_profile_name.as_ref(), self.current_view_table_name.as_ref())
{
let path = format!("{}/{}", profile, table);
if let Some(editor) = self.form_editor.get(&path) {
return matches!(editor.mode(), canvas::AppMode::Edit);
}
}
false
} }
// Mutable editor accessor
pub fn editor_for_path(&mut self, path: &str) -> Option<&mut FormEditor<FormState>> { pub fn editor_for_path(&mut self, path: &str) -> Option<&mut FormEditor<FormState>> {
self.form_editor.get_mut(path) self.form_editor.get_mut(path)
} }
// Mutable FormState accessor
pub fn form_state_for_path(&mut self, path: &str) -> Option<&mut FormState> { pub fn form_state_for_path(&mut self, path: &str) -> Option<&mut FormState> {
self.form_editor.get_mut(path).map(|e| e.data_provider_mut()) self.form_editor
.get_mut(path)
.map(|e| e.data_provider_mut())
}
// Immutable editor accessor
pub fn editor_for_path_ref(&self, path: &str) -> Option<&FormEditor<FormState>> {
self.form_editor.get(path)
}
// Immutable FormState accessor
pub fn form_state_for_path_ref(&self, path: &str) -> Option<&FormState> {
self.form_editor.get(path).map(|e| e.data_provider())
} }
pub fn ensure_form_editor<F>(&mut self, path: &str, config: &Config, loader: F) pub fn ensure_form_editor<F>(&mut self, path: &str, config: &Config, loader: F)

View File

@@ -22,6 +22,7 @@ use crate::buffer::state::BufferState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel}; use crate::bottom_panel::layout::{bottom_panel_constraints, render_bottom_panel};
use canvas::FormEditor;
use ratatui::{ use ratatui::{
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
Frame, Frame,
@@ -114,7 +115,7 @@ pub fn render_ui(
app_state, app_state,
state, state,
), ),
Page::Form(state) => { Page::Form(path) => {
let (sidebar_area, form_actual_area) = let (sidebar_area, form_actual_area) =
calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area); calculate_sidebar_layout(app_state.ui.show_sidebar, main_content_area);
if let Some(sidebar_rect) = sidebar_area { if let Some(sidebar_rect) = sidebar_area {
@@ -143,16 +144,24 @@ pub fn render_ui(
.split(form_actual_area)[1] .split(form_actual_area)[1]
}; };
render_form_page( if let Some(editor) = app_state.editor_for_path_ref(path) {
f, let (total_count, current_position) =
form_render_area, if let Some(fs) = app_state.form_state_for_path_ref(path) {
app_state, (fs.total_count, fs.current_position)
state, } else {
app_state.current_view_table_name.as_deref().unwrap_or(""), (0, 1)
theme, };
state.total_count,
state.current_position, render_form_page(
); f,
form_render_area,
editor,
app_state.current_view_table_name.as_deref().unwrap_or(""),
theme,
total_count,
current_position,
);
}
} }
} }
@@ -186,6 +195,7 @@ pub fn render_ui(
theme, theme,
current_fps, current_fps,
app_state, app_state,
router,
navigation_state, navigation_state,
event_handler_command_input, event_handler_command_input,
event_handler_command_mode_active, event_handler_command_mode_active,

View File

@@ -118,10 +118,10 @@ pub async fn run_ui() -> Result<()> {
FormState::new(initial_profile.clone(), initial_table.clone(), initial_field_defs) FormState::new(initial_profile.clone(), initial_table.clone(), initial_field_defs)
}); });
buffer_state.update_history(AppView::Form(path.clone())); buffer_state.update_history(AppView::Form(path.clone()));
router.navigate(Page::Form(path)); router.navigate(Page::Form(path.clone()));
// Fetch initial count using app_state accessor // Fetch initial count using app_state accessor
if let Some(form_state) = app_state.active_form_state_mut(&buffer_state) { if let Some(form_state) = app_state.form_state_for_path(&path) {
UiService::fetch_and_set_table_count(&mut grpc_client, form_state) UiService::fetch_and_set_table_count(&mut grpc_client, form_state)
.await .await
.context(format!( .context(format!(
@@ -154,9 +154,11 @@ pub async fn run_ui() -> Result<()> {
let mut table_just_switched = false; let mut table_just_switched = false;
loop { loop {
let position_before_event = app_state.active_form_state_mut(&buffer_state) let position_before_event = if let Page::Form(path) = &router.current {
.map(|fs| fs.current_position) app_state.form_state_for_path(path).map(|fs| fs.current_position).unwrap_or(1)
.unwrap_or(1); } else {
1
};
let mut event_processed = false; let mut event_processed = false;
// --- CHANNEL RECEIVERS --- // --- CHANNEL RECEIVERS ---
@@ -181,16 +183,18 @@ pub async fn run_ui() -> Result<()> {
// --- ADDED: For live form autocomplete --- // --- ADDED: For live form autocomplete ---
match event_handler.autocomplete_result_receiver.try_recv() { match event_handler.autocomplete_result_receiver.try_recv() {
Ok(hits) => { Ok(hits) => {
if let Some(form_state) = app_state.form_state_mut() { if let Page::Form(path) = &router.current {
if form_state.autocomplete_active { if let Some(form_state) = app_state.form_state_for_path(path) {
form_state.autocomplete_suggestions = hits; if form_state.autocomplete_active {
form_state.autocomplete_loading = false; form_state.autocomplete_suggestions = hits;
if !form_state.autocomplete_suggestions.is_empty() { form_state.autocomplete_loading = false;
form_state.selected_suggestion_index = Some(0); if !form_state.autocomplete_suggestions.is_empty() {
} else { form_state.selected_suggestion_index = Some(0);
form_state.selected_suggestion_index = None; } else {
form_state.selected_suggestion_index = None;
}
event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len());
} }
event_handler.command_message = format!("Found {} suggestions.", form_state.autocomplete_suggestions.len());
} }
} }
needs_redraw = true; needs_redraw = true;
@@ -218,9 +222,9 @@ pub async fn run_ui() -> Result<()> {
|| app_state.ui.show_search_palette || app_state.ui.show_search_palette
|| event_handler.navigation_state.active; || event_handler.navigation_state.active;
if !overlay_active { if !overlay_active {
if let Page::Form(_) = &router.current { if let Page::Form(path) = &router.current {
if !app_state.ui.focus_outside_canvas { if !app_state.ui.focus_outside_canvas {
if let Some(editor) = app_state.active_form_editor_mut(&buffer_state) { if let Some(editor) = app_state.editor_for_path(path) {
match editor.handle_key_event(*key_event) { match editor.handle_key_event(*key_event) {
KeyEventOutcome::Consumed(Some(msg)) => { KeyEventOutcome::Consumed(Some(msg)) => {
event_handler.command_message = msg; event_handler.command_message = msg;
@@ -245,9 +249,7 @@ pub async fn run_ui() -> Result<()> {
} }
} }
// Get form state from app_state and pass to handle_event // Call handle_event directly
let form_state = app_state.form_state_mut().unwrap();
let event_outcome_result = event_handler.handle_event( let event_outcome_result = event_handler.handle_event(
event, event,
&config, &config,
@@ -272,20 +274,22 @@ pub async fn run_ui() -> Result<()> {
} }
EventOutcome::DataSaved(save_outcome, message) => { EventOutcome::DataSaved(save_outcome, message) => {
event_handler.command_message = message; event_handler.command_message = message;
// Clone form_state to avoid double borrow if let Page::Form(path) = &router.current {
let mut temp_form_state = app_state.form_state().unwrap().clone(); if let Some(mut temp_form_state) = app_state.form_state_for_path(path).cloned() {
if let Err(e) = UiService::handle_save_outcome( if let Err(e) = UiService::handle_save_outcome(
save_outcome, save_outcome,
&mut grpc_client, &mut grpc_client,
&mut app_state, &mut app_state,
&mut temp_form_state, &mut temp_form_state,
).await { ).await {
event_handler.command_message = event_handler.command_message =
format!("Error handling save outcome: {}", e); format!("Error handling save outcome: {}", e);
} }
// Update app_state with changes // Update app_state with changes
if let Some(form_state) = app_state.form_state_mut() { if let Some(form_state) = app_state.form_state_for_path(path) {
*form_state = temp_form_state; *form_state = temp_form_state;
}
}
} }
} }
EventOutcome::ButtonSelected { .. } => {} EventOutcome::ButtonSelected { .. } => {}
@@ -296,7 +300,7 @@ pub async fn run_ui() -> Result<()> {
let table_name = parts[1].to_string(); let table_name = parts[1].to_string();
app_state.set_current_view_table(profile_name, table_name); app_state.set_current_view_table(profile_name, table_name);
buffer_state.update_history(AppView::Form); buffer_state.update_history(AppView::Form(path.clone()));
event_handler.command_message = format!("Loading table: {}", path); event_handler.command_message = format!("Loading table: {}", path);
} else { } else {
event_handler.command_message = format!("Invalid table path: {}", path); event_handler.command_message = format!("Invalid table path: {}", path);
@@ -382,7 +386,7 @@ pub async fn run_ui() -> Result<()> {
router.navigate(Page::Intro(intro_state.clone())); router.navigate(Page::Intro(intro_state.clone()));
} }
AppView::Login => { AppView::Login => {
// Do not re-create the page every frame. If were already on Login, // Do not re-create the page every frame. If we're already on Login,
// keep it. If we just switched into Login, create it once and // keep it. If we just switched into Login, create it once and
// inject the keymap. // inject the keymap.
if let Page::Login(_) = &router.current { if let Page::Login(_) = &router.current {
@@ -440,16 +444,15 @@ pub async fn run_ui() -> Result<()> {
} }
AppView::AddTable => router.navigate(Page::AddTable(admin_state.add_table_state.clone())), AppView::AddTable => router.navigate(Page::AddTable(admin_state.add_table_state.clone())),
AppView::AddLogic => router.navigate(Page::AddLogic(admin_state.add_logic_state.clone())), AppView::AddLogic => router.navigate(Page::AddLogic(admin_state.add_logic_state.clone())),
AppView::Form => { AppView::Form(path) => {
if let Some(form_state) = app_state.form_state().cloned() { // Router now carries the path; just navigate with it
router.navigate(Page::Form(form_state)); router.navigate(Page::Form(path.clone()));
}
} }
AppView::Scratch => {} AppView::Scratch => {}
} }
} }
if let Page::Form(_) = &router.current { if let Page::Form(current_path) = &router.current {
let current_view_profile = app_state.current_view_profile_name.clone(); let current_view_profile = app_state.current_view_profile_name.clone();
let current_view_table = app_state.current_view_table_name.clone(); let current_view_table = app_state.current_view_table_name.clone();
@@ -475,9 +478,10 @@ pub async fn run_ui() -> Result<()> {
{ {
Ok(new_form_state) => { Ok(new_form_state) => {
// Set the new form state and fetch count // Set the new form state and fetch count
app_state.set_form_state(new_form_state, &config); let path = format!("{}/{}", prof_name, tbl_name);
app_state.ensure_form_editor(&path, &config, || new_form_state);
if let Some(form_state) = app_state.form_state_mut() { if let Some(form_state) = app_state.form_state_for_path(&path) {
if let Err(e) = UiService::fetch_and_set_table_count( if let Err(e) = UiService::fetch_and_set_table_count(
&mut grpc_client, &mut grpc_client,
form_state, form_state,
@@ -596,18 +600,20 @@ pub async fn run_ui() -> Result<()> {
} }
} }
let current_position = app_state.form_state() let current_position = if let Page::Form(path) = &router.current {
.map(|fs| fs.current_position) app_state.form_state_for_path(path).map(|fs| fs.current_position).unwrap_or(1)
.unwrap_or(1); } else {
1
};
let position_changed = current_position != position_before_event; let position_changed = current_position != position_before_event;
let mut position_logic_needs_redraw = false; let mut position_logic_needs_redraw = false;
if let Page::Form(form_state) = &mut router.current { if let Page::Form(path) = &router.current {
if !table_just_switched { if !table_just_switched {
if position_changed && !app_state.is_canvas_edit_mode() { if position_changed && !app_state.is_canvas_edit_mode() {
position_logic_needs_redraw = true; position_logic_needs_redraw = true;
if let Some(form_state) = app_state.form_state_mut() { if let Some(form_state) = app_state.form_state_for_path(path) {
if form_state.current_position > form_state.total_count { if form_state.current_position > form_state.total_count {
form_state.reset_to_empty(); form_state.reset_to_empty();
event_handler.command_message = format!( event_handler.command_message = format!(
@@ -643,7 +649,7 @@ pub async fn run_ui() -> Result<()> {
event_handler.ideal_cursor_column.min(max_cursor_pos); event_handler.ideal_cursor_column.min(max_cursor_pos);
} }
} else if !position_changed && !app_state.is_canvas_edit_mode() { } else if !position_changed && !app_state.is_canvas_edit_mode() {
if let Some(form_state) = app_state.form_state_mut() { if let Some(form_state) = app_state.form_state_for_path(path) {
let current_input_str = form_state.get_current_input(); let current_input_str = form_state.get_current_input();
let current_input_len = current_input_str.chars().count(); let current_input_len = current_input_str.chars().count();
let max_cursor_pos = if current_input_len > 0 { let max_cursor_pos = if current_input_len > 0 {
@@ -709,8 +715,10 @@ pub async fn run_ui() -> Result<()> {
terminal.show_cursor()?; terminal.show_cursor()?;
} else { } else {
// Inside canvas → let canvas handle it // Inside canvas → let canvas handle it
if let Some(editor) = &app_state.form_editor { if let Page::Form(path) = &router.current {
let _ = CursorManager::update_for_mode(editor.mode()); if let Some(editor) = app_state.editor_for_path(path) {
let _ = CursorManager::update_for_mode(editor.mode());
}
} }
if let Page::Login(page) = &router.current { if let Page::Login(page) = &router.current {
let _ = CursorManager::update_for_mode(page.editor.mode()); let _ = CursorManager::update_for_mode(page.editor.mode());
@@ -727,16 +735,9 @@ pub async fn run_ui() -> Result<()> {
} }
} }
// Workaround for borrow checker
let current_dir = app_state.current_dir.clone(); let current_dir = app_state.current_dir.clone();
let form_state_clone = if let Page::Form(path) = &router.current { terminal
app_state.form_state_for_path(path).cloned() .draw(|f| {
} else {
None
};
if let Some(form_state_clone) = form_state_clone {
terminal.draw(|f| {
render_ui( render_ui(
f, f,
&mut router, &mut router,
@@ -753,7 +754,6 @@ pub async fn run_ui() -> Result<()> {
}) })
.context("Terminal draw call failed")?; .context("Terminal draw call failed")?;
needs_redraw = false; needs_redraw = false;
}
} }
let now = Instant::now(); let now = Instant::now();