use crate::document::EditDelta;
use crate::editor::vim_ops::motion::{first_non_blank, line_end_offset, OpRange};
use crate::editor::EditorState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operator {
Delete,
Change,
Yank,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpResult {
pub register_text: String,
pub linewise: bool,
pub enter_insert: bool,
}
pub fn execute_operator(editor: &mut EditorState, op: Operator, range: OpRange) -> OpResult {
match range {
OpRange::Chars(r) => exec_charwise(editor, op, r.start, r.end),
OpRange::Lines { first, last } => exec_linewise(editor, op, first, last),
}
}
fn exec_charwise(editor: &mut EditorState, op: Operator, start: usize, end: usize) -> OpResult {
let len = editor.buffer.len_chars();
let start = start.min(len);
let end = end.min(len);
let text = if start < end {
editor.buffer.slice_to_string(start, end)
} else {
String::new()
};
match op {
Operator::Yank => {
editor.flash_yank(start, end);
editor.cursor.offset = start;
editor.cursor.preferred_col = editor.cursor.cell_col(&editor.buffer);
editor.update_cursor_block();
}
Operator::Delete | Operator::Change => {
if start < end {
editor.apply_delta(EditDelta {
offset: start,
removed: text.clone(),
inserted: String::new(),
});
} else {
editor.cursor.offset = start;
}
}
}
OpResult {
register_text: text,
linewise: false,
enter_insert: op == Operator::Change,
}
}
fn exec_linewise(editor: &mut EditorState, op: Operator, first: usize, last: usize) -> OpResult {
let buf = &editor.buffer;
let line_count = buf.line_count();
if line_count == 0 {
return OpResult {
register_text: String::new(),
linewise: true,
enter_insert: op == Operator::Change,
};
}
let first = first.min(line_count - 1);
let last = last.min(line_count - 1).max(first);
let content_start = buf.line_to_char(first);
let len = buf.len_chars();
let block_end = if last + 1 < line_count {
buf.line_to_char(last + 1)
} else {
len
};
let mut register_text = buf.slice_to_string(content_start, block_end);
if !register_text.ends_with('\n') {
register_text.push('\n');
}
match op {
Operator::Yank => {
editor.flash_yank(content_start, block_end);
editor.cursor.offset = content_start;
editor.cursor.preferred_col = editor.cursor.cell_col(&editor.buffer);
editor.update_cursor_block();
}
Operator::Delete => {
let (del_start, del_end) = if block_end >= len && content_start > 0 {
(content_start - 1, len)
} else {
(content_start, block_end)
};
let removed = editor.buffer.slice_to_string(del_start, del_end);
editor.apply_delta(EditDelta {
offset: del_start,
removed,
inserted: String::new(),
});
let landing = del_start.min(editor.buffer.len_chars());
let line = editor.buffer.char_to_line(landing);
editor.cursor.offset = first_non_blank(&editor.buffer, line);
editor.cursor.preferred_col = editor.cursor.cell_col(&editor.buffer);
editor.update_cursor_block();
}
Operator::Change => {
let content_end = line_end_offset(&editor.buffer, last);
if content_start < content_end {
let removed = editor.buffer.slice_to_string(content_start, content_end);
editor.apply_delta(EditDelta {
offset: content_start,
removed,
inserted: String::new(),
});
}
editor.cursor.offset = content_start.min(editor.buffer.len_chars());
editor.cursor.preferred_col = editor.cursor.cell_col(&editor.buffer);
editor.update_cursor_block();
}
}
OpResult {
register_text,
linewise: true,
enter_insert: op == Operator::Change,
}
}