#[cfg(feature = "cursor-style")]
use crate::cursor::CursorManager;
use crate::DataProvider;
use crate::canvas::modes::AppMode;
use crate::canvas::state::SelectionState;
use crate::editor::EditorCore;
#[cfg(feature = "keybindings")]
use crate::editor::behavior::KeybindingParadigm;
impl<D: DataProvider> EditorCore<D> {
pub(crate) fn set_highlight_mode_selection(&mut self, selection: SelectionState) {
self.ui_state.current_mode = AppMode::Sel;
self.ui_state.selection = selection;
#[cfg(feature = "cursor-style")]
{
let _ = CursorManager::update_for_mode(AppMode::Sel);
}
}
pub fn set_mode(&mut self, mode: AppMode) {
#[cfg(not(feature = "textmode-normal"))]
if self.ui_state.current_mode != mode {
self.break_undo_coalescing();
}
#[cfg(feature = "textmode-normal")]
let _ = mode;
#[cfg(feature = "textmode-normal")]
{
self.ui_state.current_mode = AppMode::Ins;
self.ui_state.selection = SelectionState::None;
#[cfg(feature = "cursor-style")]
{
let _ = CursorManager::update_for_mode(AppMode::Ins);
}
}
#[cfg(not(feature = "textmode-normal"))]
{
#[cfg(feature = "keybindings")]
{
match self.keybinding_paradigm() {
KeybindingParadigm::Helix => self.set_mode_helix(mode),
KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
self.set_mode_emacs(mode)
}
KeybindingParadigm::Vim => self.set_mode_vim(mode),
}
}
#[cfg(not(feature = "keybindings"))]
self.set_mode_vim(mode);
}
}
pub fn exit_edit_mode(&mut self) -> anyhow::Result<()> {
#[cfg(feature = "validation")]
{
let current_text = self.current_text();
if !self
.ui_state
.validation
.allows_field_switch(self.ui_state.current_field, current_text)
{
if let Some(reason) = self
.ui_state
.validation
.field_switch_block_reason(self.ui_state.current_field, current_text)
{
self.ui_state
.validation
.set_last_switch_block(reason.clone());
return Err(anyhow::anyhow!("Cannot exit edit mode: {}", reason));
}
}
}
let current_text = self.current_text();
if !current_text.is_empty() {
let max_normal_pos = current_text.chars().count().saturating_sub(1);
if self.ui_state.cursor_pos > max_normal_pos {
self.set_cursor_raw(max_normal_pos);
}
}
#[cfg(feature = "validation")]
{
let field_index = self.ui_state.current_field;
if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
if cfg.external_validation_enabled {
let text = self.current_text().to_string();
if !text.is_empty() {
self.set_external_validation(
field_index,
crate::validation::ExternalValidationState::Validating,
);
if let Some(cb) = self.external_validation_callback.as_mut() {
let final_state = cb(field_index, &text);
self.set_external_validation(field_index, final_state);
}
}
}
}
}
#[cfg(feature = "textmode-normal")]
{
#[cfg(feature = "suggestions")]
{
self.dismiss_suggestions();
}
Ok(())
}
#[cfg(not(feature = "textmode-normal"))]
{
self.set_mode(AppMode::Nor);
#[cfg(feature = "suggestions")]
{
self.dismiss_suggestions();
}
Ok(())
}
}
pub fn enter_edit_mode(&mut self) {
#[cfg(feature = "computed")]
{
if let Some(computed_state) = &self.ui_state.computed {
if computed_state.is_computed_field(self.ui_state.current_field) {
return;
}
}
}
#[cfg(feature = "textmode-normal")]
{
self.ui_state.current_mode = AppMode::Ins;
self.ui_state.selection = SelectionState::None;
#[cfg(feature = "cursor-style")]
{
let _ = CursorManager::update_for_mode(AppMode::Ins);
}
}
#[cfg(not(feature = "textmode-normal"))]
{
#[cfg(feature = "keybindings")]
match self.keybinding_paradigm() {
KeybindingParadigm::Helix => self.enter_edit_mode_helix(),
KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
self.enter_edit_mode_emacs()
}
KeybindingParadigm::Vim => self.enter_edit_mode_vim(),
}
#[cfg(not(feature = "keybindings"))]
self.enter_edit_mode_vim();
}
#[cfg(feature = "suggestions")]
self.check_suggestion_trigger();
}
pub fn enter_highlight_mode(&mut self) {
#[cfg(feature = "textmode-normal")]
{}
#[cfg(not(feature = "textmode-normal"))]
{
#[cfg(feature = "keybindings")]
match self.keybinding_paradigm() {
KeybindingParadigm::Helix => self.enter_highlight_mode_helix(),
KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
self.enter_highlight_mode_emacs()
}
KeybindingParadigm::Vim => self.enter_highlight_mode_vim(),
}
#[cfg(not(feature = "keybindings"))]
self.enter_highlight_mode_vim();
}
}
pub fn enter_highlight_line_mode(&mut self) {
#[cfg(feature = "textmode-normal")]
{}
#[cfg(not(feature = "textmode-normal"))]
{
#[cfg(feature = "keybindings")]
match self.keybinding_paradigm() {
KeybindingParadigm::Helix => self.enter_highlight_line_mode_helix(),
KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
self.enter_highlight_line_mode_emacs()
}
KeybindingParadigm::Vim => self.enter_highlight_line_mode_vim(),
}
#[cfg(not(feature = "keybindings"))]
self.enter_highlight_line_mode_vim();
}
}
pub fn exit_highlight_mode(&mut self) {
#[cfg(feature = "textmode-normal")]
{}
#[cfg(not(feature = "textmode-normal"))]
{
#[cfg(feature = "keybindings")]
match self.keybinding_paradigm() {
KeybindingParadigm::Helix => self.exit_highlight_mode_helix(),
KeybindingParadigm::Emacs | KeybindingParadigm::Vscode => {
self.exit_highlight_mode_emacs()
}
KeybindingParadigm::Vim => self.exit_highlight_mode_vim(),
}
#[cfg(not(feature = "keybindings"))]
self.exit_highlight_mode_vim();
}
}
pub fn is_highlight_mode(&self) -> bool {
#[cfg(feature = "textmode-normal")]
{
false
}
#[cfg(not(feature = "textmode-normal"))]
{
return self.ui_state.current_mode == AppMode::Sel;
}
}
pub fn selection_state(&self) -> &SelectionState {
&self.ui_state.selection
}
pub fn move_left_with_selection(&mut self) {
let _ = self.move_left();
}
pub fn move_right_with_selection(&mut self) {
let _ = self.move_right();
}
pub fn move_up_with_selection(&mut self) {
let _ = self.move_up();
}
pub fn move_down_with_selection(&mut self) {
let _ = self.move_down();
}
pub fn move_word_next_with_selection(&mut self) {
self.move_word_next();
}
pub fn move_word_end_with_selection(&mut self) {
self.move_word_end();
}
pub fn move_word_prev_with_selection(&mut self) {
self.move_word_prev();
}
pub fn move_word_end_prev_with_selection(&mut self) {
self.move_word_end_prev();
}
pub fn move_big_word_next_with_selection(&mut self) {
self.move_big_word_next();
}
pub fn move_big_word_end_with_selection(&mut self) {
self.move_big_word_end();
}
pub fn move_big_word_prev_with_selection(&mut self) {
self.move_big_word_prev();
}
pub fn move_big_word_end_prev_with_selection(&mut self) {
self.move_big_word_end_prev();
}
pub fn move_line_start_with_selection(&mut self) {
self.move_line_start();
}
pub fn move_line_end_with_selection(&mut self) {
self.move_line_end();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::canvas::modes::AppMode;
#[derive(Clone)]
struct TestProvider {
fields: Vec<(&'static str, String)>,
}
impl TestProvider {
fn new(values: &[&'static str]) -> Self {
Self {
fields: values
.iter()
.enumerate()
.map(|(i, value)| {
let name = match i {
0 => "a",
1 => "b",
_ => "c",
};
(name, (*value).to_string())
})
.collect(),
}
}
}
impl DataProvider for TestProvider {
fn field_count(&self) -> usize {
self.fields.len()
}
fn field_name(&self, index: usize) -> &str {
self.fields[index].0
}
fn field_value(&self, index: usize) -> &str {
&self.fields[index].1
}
fn set_field_value(&mut self, index: usize, value: String) {
self.fields[index].1 = value;
}
}
#[test]
fn visual_characterwise_toggles_and_switches_from_linewise() {
let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta"]));
editor.enter_highlight_mode();
assert_eq!(editor.mode(), AppMode::Sel);
assert!(matches!(
editor.selection_state(),
SelectionState::Characterwise { anchor: (0, 0) }
));
editor.enter_highlight_mode();
assert_eq!(editor.mode(), AppMode::Nor);
assert!(matches!(editor.selection_state(), SelectionState::None));
editor.enter_highlight_line_mode();
assert!(matches!(
editor.selection_state(),
SelectionState::Linewise { anchor_field: 0 }
));
editor.move_down();
editor.enter_highlight_mode();
assert!(matches!(
editor.selection_state(),
SelectionState::Characterwise { anchor: (1, 0) }
));
}
#[test]
fn visual_linewise_toggles_and_switches_from_characterwise() {
let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta"]));
editor.enter_highlight_line_mode();
assert_eq!(editor.mode(), AppMode::Sel);
assert!(matches!(
editor.selection_state(),
SelectionState::Linewise { anchor_field: 0 }
));
editor.enter_highlight_line_mode();
assert_eq!(editor.mode(), AppMode::Nor);
assert!(matches!(editor.selection_state(), SelectionState::None));
editor.enter_highlight_mode();
editor.move_down();
editor.enter_highlight_line_mode();
assert!(matches!(
editor.selection_state(),
SelectionState::Linewise { anchor_field: 0 }
));
}
#[test]
fn visual_linewise_switch_preserves_selected_line_range() {
let mut editor = EditorCore::new(TestProvider::new(&["alpha", "beta", "gamma"]));
editor.enter_highlight_mode();
editor.move_down();
editor.enter_highlight_line_mode();
assert_eq!(editor.current_field(), 1);
assert!(matches!(
editor.selection_state(),
SelectionState::Linewise { anchor_field: 0 }
));
}
}