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)]
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);
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()
}
};
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);
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())
}
}