178 lines
5.7 KiB
Rust
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()
|
|
}
|
|
}
|