rsvim_core 0.1.3-alpha.2

The core library for RSVIM text editor.
Documentation
//! The insert mode.

use crate::buf::opt::EndOfLineOption;
use crate::buf::undo;
use crate::chan;
use crate::chan::MasterMessage;
use crate::prelude::*;
use crate::state::State;
use crate::state::StateContext;
use crate::state::Stateful;
use crate::state::ops::CursorInsertPayload;
use crate::state::ops::Operation;
use crate::state::ops::cursor_ops;
use crate::syntax;
use crate::syntax::SyntaxEdit;
use crate::syntax::SyntaxEditUpdate;
use crate::ui::canvas::CursorStyle;
use crate::ui::tree::*;
use compact_str::CompactString;
use compact_str::ToCompactString;
use crossterm::event::Event;
use crossterm::event::KeyCode;
use crossterm::event::KeyEventKind;

#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
/// The finite-state-machine for insert mode.
pub struct Insert {}

impl Insert {
  fn get_operation(&self, event: &Event) -> Option<Operation> {
    match event {
      Event::FocusGained => None,
      Event::FocusLost => None,
      Event::Key(key_event) => match key_event.kind {
        KeyEventKind::Press => {
          trace!("Event::key:{:?}", key_event);
          match key_event.code {
            KeyCode::Up => Some(Operation::CursorMoveUpBy(1)),
            KeyCode::Down => Some(Operation::CursorMoveDownBy(1)),
            KeyCode::Left => Some(Operation::CursorMoveLeftBy(1)),
            KeyCode::Right => Some(Operation::CursorMoveRightBy(1)),
            KeyCode::Home => Some(Operation::CursorMoveLeftBy(usize::MAX)),
            KeyCode::End => Some(Operation::CursorMoveRightBy(usize::MAX)),
            KeyCode::Char(c) => Some(Operation::CursorInsert(
              CursorInsertPayload::Text(c.to_compact_string()),
            )),
            KeyCode::Tab => {
              Some(Operation::CursorInsert(CursorInsertPayload::Tab))
            }
            KeyCode::Enter => {
              Some(Operation::CursorInsert(CursorInsertPayload::Eol))
            }
            KeyCode::Backspace => Some(Operation::CursorDelete(-1)),
            KeyCode::Delete => Some(Operation::CursorDelete(1)),
            KeyCode::Esc => Some(Operation::GotoNormalMode),
            _ => None,
          }
        }
        KeyEventKind::Repeat => None,
        KeyEventKind::Release => None,
      },
      Event::Mouse(_mouse_event) => None,
      Event::Paste(_paste_string) => None,
      Event::Resize(_columns, _rows) => None,
    }
  }
}

impl Stateful for Insert {
  fn handle(&self, context: &StateContext, event: Event) -> State {
    if let Some(op) = self.get_operation(&event) {
      return self.handle_op(context, op);
    }

    State::Insert(Insert::default())
  }

  fn handle_op(&self, context: &StateContext, op: Operation) -> State {
    match op {
      Operation::GotoNormalMode => self.goto_normal_mode(context),
      Operation::CursorMoveBy((_, _))
      | Operation::CursorMoveUpBy(_)
      | Operation::CursorMoveDownBy(_)
      | Operation::CursorMoveLeftBy(_)
      | Operation::CursorMoveRightBy(_)
      | Operation::CursorMoveTo((_, _)) => self.cursor_move(context, op),
      Operation::CursorInsert(payload) => self.cursor_insert(context, payload),
      Operation::CursorDelete(n) => self.cursor_delete(context, n),
      _ => unreachable!(),
    }
  }
}

impl Insert {
  pub fn cursor_delete(&self, context: &StateContext, n: isize) -> State {
    let tree = context.tree.clone();
    let mut tree = lock!(tree);
    let current_window = tree.current_window_mut().unwrap();
    let current_window_id = current_window.id();
    let buffer = current_window.buffer().upgrade().unwrap();
    let mut buffer = lock!(buffer);

    // Save editing change
    let absolute_char_idx_range =
      cursor_ops::cursor_absolute_delete_chars_range(
        &tree,
        current_window_id,
        buffer.text(),
        n,
      );
    if let Some(absolute_char_idx_range) = absolute_char_idx_range
      && !absolute_char_idx_range.is_empty()
    {
      let cursor_viewport = tree.editable_cursor_viewport(current_window_id);
      let cursor_line_idx = cursor_viewport.line_idx();
      let cursor_char_idx = cursor_viewport.char_idx();
      let cursor_absolute_char_idx = buffer
        .text()
        .to_absolute_char_idx(cursor_line_idx, cursor_char_idx);
      trace!(
        "cursor_line_idx:{},cursor_char_idx:{},cursor_absolute_char_idx:{}",
        cursor_line_idx, cursor_char_idx, cursor_absolute_char_idx
      );
      let payload = buffer
        .text()
        .rope()
        .chars_at(absolute_char_idx_range.start)
        .take(absolute_char_idx_range.len())
        .collect::<CompactString>();

      if cfg!(debug_assertions) {
        debug_assert_ne!(n, 0);
        if n < 0 {
          debug_assert_eq!(
            absolute_char_idx_range.end,
            buffer
              .text()
              .to_absolute_char_idx(cursor_line_idx, cursor_char_idx)
          );
        } else {
          debug_assert_eq!(
            absolute_char_idx_range.start,
            buffer
              .text()
              .to_absolute_char_idx(cursor_line_idx, cursor_char_idx)
          );
        }
      }

      let syn_delete =
        syntax::make_input_edit_by_delete(&buffer, &absolute_char_idx_range);

      let (cursor_line_idx_after, cursor_char_idx_after) =
        cursor_ops::cursor_delete(
          &mut tree,
          current_window_id,
          buffer.text_mut(),
          n,
        )
        .unwrap();
      let cursor_absolute_char_idx_after = buffer
        .text()
        .to_absolute_char_idx(cursor_line_idx_after, cursor_char_idx_after);
      trace!(
        "cursor_line_idx_after:{},cursor_char_idx_after:{},cursor_absolute_char_idx_after:{}",
        cursor_line_idx_after,
        cursor_char_idx_after,
        cursor_absolute_char_idx_after
      );
      buffer.undo_mut().current_mut().delete(undo::Delete {
        payload,
        start_char: absolute_char_idx_range.start,
        end_char: absolute_char_idx_range.end,
        cursor_char_idx_before: cursor_absolute_char_idx,
        cursor_char_idx_after: cursor_absolute_char_idx_after,
      });
      buffer.increase_editing_version();
      debug_assert_eq!(
        buffer
          .text()
          .to_absolute_char_idx(cursor_line_idx_after, cursor_char_idx_after),
        absolute_char_idx_range.start
      );

      if buffer.syntax().is_some() {
        let rope = buffer.text().rope().clone();
        let editing_version = buffer.editing_version();
        let syn = buffer.syntax_mut().as_mut().unwrap();
        debug_assert!(syn_delete.is_some());
        syn.add_pending_edits(SyntaxEdit::Update(SyntaxEditUpdate {
          payload: rope,
          input: syn_delete.unwrap(),
          version: editing_version,
        }));
        chan::send_to_master(
          context.master_tx.clone(),
          MasterMessage::SyntaxEditReq(chan::SyntaxEditReq {
            buffer_id: buffer.id(),
          }),
        );
      }
    }

    State::Insert(Insert::default())
  }
}

impl Insert {
  pub fn cursor_insert(
    &self,
    context: &StateContext,
    payload: CursorInsertPayload,
  ) -> State {
    let tree = context.tree.clone();
    let mut tree = lock!(tree);
    let current_window = tree.current_window_mut().unwrap();
    let current_window_id = current_window.id();
    let buffer = current_window.buffer().upgrade().unwrap();
    let mut buffer = lock!(buffer);

    let payload = match payload {
      CursorInsertPayload::Text(c) => c,
      CursorInsertPayload::Tab => {
        if !buffer.options().expand_tab() {
          '\t'.to_compact_string()
        } else {
          ' '
            .to_compact_string()
            .repeat(buffer.options().shift_width() as usize)
        }
      }
      CursorInsertPayload::Eol => {
        let eol = Into::<EndOfLineOption>::into(buffer.options().file_format());
        let eol = format!("{eol}");
        trace!("Insert eol:{eol:?}");
        eol.to_compact_string()
      }
    };

    // Save editing change
    let cursor_absolute_char_idx = cursor_ops::cursor_absolute_char_idx(
      &tree,
      current_window_id,
      buffer.text(),
    );
    let cursor_absolute_end_char_idx =
      cursor_absolute_char_idx + payload.chars().count();
    let syn_insert = syntax::make_input_edit_by_insert(
      &buffer,
      cursor_absolute_char_idx,
      cursor_absolute_end_char_idx,
    );

    let (cursor_line_idx_after, cursor_char_idx_after) =
      cursor_ops::cursor_insert(
        &mut tree,
        current_window_id,
        buffer.text_mut(),
        payload.clone(),
      );
    let cursor_absolute_char_idx_after = buffer
      .text()
      .to_absolute_char_idx(cursor_line_idx_after, cursor_char_idx_after);
    buffer.undo_mut().current_mut().insert(undo::Insert {
      payload: payload.clone(),
      start_char: cursor_absolute_char_idx,
      end_char: cursor_absolute_end_char_idx,
      cursor_char_idx_before: cursor_absolute_char_idx,
      cursor_char_idx_after: cursor_absolute_char_idx_after,
    });
    buffer.increase_editing_version();
    debug_assert_eq!(
      buffer
        .text()
        .to_absolute_char_idx(cursor_line_idx_after, cursor_char_idx_after),
      cursor_ops::cursor_absolute_char_idx(
        &tree,
        current_window_id,
        buffer.text(),
      )
    );
    debug_assert_eq!(
      cursor_absolute_char_idx + payload.chars().count(),
      cursor_ops::cursor_absolute_char_idx(
        &tree,
        current_window_id,
        buffer.text(),
      )
    );

    if buffer.syntax().is_some() {
      let rope = buffer.text().rope().clone();
      let editing_version = buffer.editing_version();
      let syn = buffer.syntax_mut().as_mut().unwrap();
      debug_assert!(syn_insert.is_some());
      syn.add_pending_edits(SyntaxEdit::Update(SyntaxEditUpdate {
        payload: rope,
        input: syn_insert.unwrap(),
        version: editing_version,
      }));
      chan::send_to_master(
        context.master_tx.clone(),
        MasterMessage::SyntaxEditReq(chan::SyntaxEditReq {
          buffer_id: buffer.id(),
        }),
      );
    }

    State::Insert(Insert::default())
  }
}

impl Insert {
  pub fn goto_normal_mode(&self, context: &StateContext) -> State {
    let tree = context.tree.clone();
    let mut tree = lock!(tree);
    let current_window = tree.current_window_mut().unwrap();
    let current_window_id = current_window.id();
    let buffer = current_window.buffer().upgrade().unwrap();
    let mut buffer = lock!(buffer);

    // Commit editing changes
    buffer.undo_mut().commit();

    let op = Operation::CursorMoveBy((-1, 0));
    cursor_ops::cursor_move(
      &mut tree,
      current_window_id,
      buffer.text(),
      op,
      false,
    );

    if cfg!(debug_assertions) {
      debug_assert!(tree.cursor_id().is_some());
      let cursor_id = tree.cursor_id().unwrap();
      debug_assert!(tree.parent_id(cursor_id).is_some());
      let parent_id = tree.parent_id(cursor_id).unwrap();
      debug_assert!(matches!(
        tree.node(parent_id).unwrap(),
        Node::WindowContent(_)
      ));
      debug_assert!(
        tree.parent_id(tree.parent_id(cursor_id).unwrap()).is_some()
      );
      let parent_parent_id = tree.parent_id(parent_id).unwrap();
      debug_assert!(tree.current_window_id().is_some());
      debug_assert_eq!(parent_parent_id, tree.current_window_id().unwrap());
    }

    tree
      .cursor_mut()
      .unwrap()
      .set_cursor_style(CursorStyle::SteadyBlock);

    State::Normal(super::Normal::default())
  }
}

impl Insert {
  pub fn cursor_move(&self, context: &StateContext, op: Operation) -> State {
    let tree = context.tree.clone();
    let mut tree = lock!(tree);
    let current_window = tree.current_window_mut().unwrap();
    let current_window_id = current_window.id();
    let buffer = current_window.buffer().upgrade().unwrap();
    let buffer = lock!(buffer);

    cursor_ops::cursor_move(
      &mut tree,
      current_window_id,
      buffer.text(),
      op,
      true,
    );

    State::Insert(Insert::default())
  }
}