Files
komp_ac/canvas/src/editor/navigation.rs
2025-08-15 00:06:19 +02:00

178 lines
5.7 KiB
Rust

// src/editor/navigation.rs
use crate::canvas::modes::AppMode;
use crate::editor::FormEditor;
use crate::DataProvider;
impl<D: DataProvider> FormEditor<D> {
/// Centralized field transition logic (unchanged).
pub fn transition_to_field(&mut self, new_field: usize) -> anyhow::Result<()> {
let field_count = self.data_provider.field_count();
if field_count == 0 {
return Ok(());
}
let prev_field = self.ui_state.current_field;
#[cfg(feature = "computed")]
let mut target_field = new_field.min(field_count - 1);
#[cfg(not(feature = "computed"))]
let target_field = new_field.min(field_count - 1);
#[cfg(feature = "computed")]
{
if let Some(computed_state) = &self.ui_state.computed {
if computed_state.is_computed_field(target_field) {
if target_field >= prev_field {
for i in (target_field + 1)..field_count {
if !computed_state.is_computed_field(i) {
target_field = i;
break;
}
}
} else {
let mut i = target_field;
loop {
if !computed_state.is_computed_field(i) {
target_field = i;
break;
}
if i == 0 {
break;
}
i -= 1;
}
}
}
}
}
if target_field == prev_field {
return Ok(());
}
#[cfg(feature = "validation")]
self.ui_state.validation.clear_last_switch_block();
#[cfg(feature = "validation")]
{
let current_text = self.current_text();
if !self
.ui_state
.validation
.allows_field_switch(prev_field, current_text)
{
if let Some(reason) = self
.ui_state
.validation
.field_switch_block_reason(prev_field, current_text)
{
self.ui_state
.validation
.set_last_switch_block(reason.clone());
tracing::debug!("Field switch blocked: {}", reason);
return Err(anyhow::anyhow!(
"Cannot switch fields: {}",
reason
));
}
}
}
#[cfg(feature = "validation")]
{
let text =
self.data_provider.field_value(prev_field).to_string();
let _ = self
.ui_state
.validation
.validate_field_content(prev_field, &text);
if let Some(cfg) =
self.ui_state.validation.get_field_config(prev_field)
{
if cfg.external_validation_enabled && !text.is_empty() {
self.set_external_validation(
prev_field,
crate::validation::ExternalValidationState::Validating,
);
if let Some(cb) =
self.external_validation_callback.as_mut()
{
let final_state = cb(prev_field, &text);
self.set_external_validation(prev_field, final_state);
}
}
}
}
#[cfg(feature = "computed")]
{
// Placeholder for recompute hook if needed later
}
self.ui_state.move_to_field(target_field, field_count);
let current_text = self.current_text();
let max_pos = current_text.chars().count();
self.ui_state.set_cursor(
self.ui_state.ideal_cursor_column,
max_pos,
self.ui_state.current_mode == AppMode::Edit,
);
// Automatically close suggestions on field switch
#[cfg(feature = "suggestions")]
{
self.close_suggestions();
}
Ok(())
}
/// Move to first line (vim gg)
pub fn move_first_line(&mut self) -> anyhow::Result<()> {
self.transition_to_field(0)
}
/// Move to last line (vim G)
pub fn move_last_line(&mut self) -> anyhow::Result<()> {
let last_field =
self.data_provider.field_count().saturating_sub(1);
self.transition_to_field(last_field)
}
/// Move to previous field (vim k / up)
pub fn move_up(&mut self) -> anyhow::Result<()> {
let new_field = self.ui_state.current_field.saturating_sub(1);
self.transition_to_field(new_field)
}
/// Move to next field (vim j / down)
pub fn move_down(&mut self) -> anyhow::Result<()> {
let new_field = (self.ui_state.current_field + 1)
.min(self.data_provider.field_count().saturating_sub(1));
self.transition_to_field(new_field)
}
/// Move to next field cyclic
pub fn move_to_next_field(&mut self) -> anyhow::Result<()> {
let field_count = self.data_provider.field_count();
if field_count == 0 {
return Ok(());
}
let new_field = (self.ui_state.current_field + 1) % field_count;
self.transition_to_field(new_field)
}
/// Aliases
pub fn prev_field(&mut self) -> anyhow::Result<()> {
self.move_up()
}
pub fn next_field(&mut self) -> anyhow::Result<()> {
self.move_down()
}
}