use std::collections::HashMap;
use super::{scroll_editor_content, DeleteSelectionWith};
use crate::{caret_locate::{locate_col, CaretColLocationInLine},
caret_scroll_index, col, empty_check_early_return, inline_string,
multiline_disabled_check_early_return, row, validate_buffer_mut, CaretScrAdj, ColIndex,
EditorArgsMut, EditorBuffer, EditorEngine, GCString, GCStringExt,
InlineString, InlineVec, RowIndex, SelectionRange, Width};
pub fn insert_chunk_at_caret(args: EditorArgsMut<'_>, chunk: &str) {
let EditorArgsMut { buffer, engine } = args;
let caret_scr_adj = buffer.get_caret_scr_adj();
let row_index_scr_adj = caret_scr_adj.row_index;
if buffer.line_at_row_index(row_index_scr_adj).is_some() {
insert_into_existing_line(EditorArgsMut { engine, buffer }, caret_scr_adj, chunk);
} else {
fill_in_missing_lines_up_to_row(
EditorArgsMut { engine, buffer },
row_index_scr_adj,
);
insert_chunk_into_new_line(
EditorArgsMut { engine, buffer },
caret_scr_adj,
chunk,
);
}
}
pub fn insert_lines_batch_at_caret(args: EditorArgsMut<'_>, lines: &[&str]) {
let EditorArgsMut { buffer, engine } = args;
if lines.is_empty() {
return;
}
let mut buffer_mut = buffer.get_mut(engine.viewport());
let line_count = lines.len();
for (index, line_content) in lines.iter().enumerate() {
let current_caret_scr_adj = *buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs;
let row_index = current_caret_scr_adj.row_index;
if let Some(existing_line) = buffer_mut.inner.lines.get(row_index.as_usize()) {
let (new_line_content, chunk_display_width) =
existing_line.insert_chunk_at_col(current_caret_scr_adj.col_index, line_content);
buffer_mut.inner.lines[row_index.as_usize()] = new_line_content.grapheme_string();
let new_line_display_width = buffer_mut.inner.lines[row_index.as_usize()].display_width;
scroll_editor_content::inc_caret_col_by(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
chunk_display_width,
new_line_display_width,
buffer_mut.inner.vp.col_width,
);
} else {
fill_in_missing_lines_up_to_row_impl(&mut buffer_mut, row_index);
if buffer_mut.inner.lines.get(row_index.as_usize()).is_some() {
buffer_mut.inner.lines[row_index.as_usize()] = line_content.grapheme_string();
let line_display_width = buffer_mut.inner.lines[row_index.as_usize()].display_width;
let col_amt = GCString::width(line_content);
scroll_editor_content::inc_caret_col_by(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
col_amt,
line_display_width,
buffer_mut.inner.vp.col_width,
);
}
}
if index < line_count - 1 {
match locate_col_impl(&buffer_mut) {
CaretColLocationInLine::AtEnd => {
let new_row_index = scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
scroll_editor_content::reset_caret_col(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
);
buffer_mut.inner.lines.insert(new_row_index.as_usize(), "".grapheme_string());
}
CaretColLocationInLine::AtStart => {
let cur_row_index = (*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
buffer_mut.inner.lines.insert(cur_row_index.as_usize(), "".grapheme_string());
scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
}
CaretColLocationInLine::InMiddle => {
let caret_scr_adj = *buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs;
let row_index = caret_scr_adj.row_index.as_usize();
if let Some(line) = buffer_mut.inner.lines.get(row_index).cloned() {
let col_index = caret_scr_adj.col_index;
if let Some((left_string, right_string)) = line.split_at_display_col(col_index) {
buffer_mut.inner.lines[row_index] = left_string.grapheme_string();
buffer_mut.inner.lines.insert(row_index + 1, right_string.grapheme_string());
scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
scroll_editor_content::reset_caret_col(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
);
}
}
}
}
}
}
}
fn locate_col_impl(buffer_mut: &validate_buffer_mut::EditorBufferMutWithDrop<'_>) -> CaretColLocationInLine {
let caret_scr_adj = *buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs;
let row_index = caret_scr_adj.row_index;
if let Some(line) = buffer_mut.inner.lines.get(row_index.as_usize()) {
let col_index = caret_scr_adj.col_index;
let line_width = line.display_width;
if col_index == col(0) {
CaretColLocationInLine::AtStart
} else if col_index >= caret_scroll_index::col_index_for_width(line_width) {
CaretColLocationInLine::AtEnd
} else {
CaretColLocationInLine::InMiddle
}
} else {
CaretColLocationInLine::AtEnd
}
}
fn fill_in_missing_lines_up_to_row_impl(buffer_mut: &mut validate_buffer_mut::EditorBufferMutWithDrop<'_>, row_index: RowIndex) {
let max_row_index = row_index.as_usize();
if buffer_mut.inner.lines.get(max_row_index).is_none() {
for row_idx in 0..=max_row_index {
if buffer_mut.inner.lines.get(row_idx).is_none() {
buffer_mut.inner.lines.push("".grapheme_string());
}
}
}
}
pub fn insert_new_line_at_caret(args: EditorArgsMut<'_>) {
let EditorArgsMut { buffer, engine } = args;
multiline_disabled_check_early_return!(engine, @Nothing);
if buffer.is_empty() {
{
let buffer_mut = buffer.get_mut(engine.viewport());
buffer_mut.inner.lines.push("".grapheme_string());
}
return;
}
match locate_col(buffer) {
CaretColLocationInLine::AtEnd => {
insert_new_line_at_caret_helper::insert_new_line_at_end_of_current_line(
EditorArgsMut { engine, buffer },
);
}
CaretColLocationInLine::AtStart => {
insert_new_line_at_caret_helper::insert_new_line_at_start_of_current_line(
EditorArgsMut { engine, buffer },
);
}
CaretColLocationInLine::InMiddle => {
insert_new_line_at_caret_helper::insert_new_line_at_middle_of_current_line(
EditorArgsMut { engine, buffer },
);
}
}
}
mod insert_new_line_at_caret_helper {
use super::{scroll_editor_content, EditorArgsMut, GCString, GCStringExt};
pub fn insert_new_line_at_end_of_current_line(args: EditorArgsMut<'_>) {
let EditorArgsMut { buffer, engine } = args;
{
let buffer_mut = buffer.get_mut(engine.viewport());
let new_row_index = scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
scroll_editor_content::reset_caret_col(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
);
buffer_mut
.inner
.lines
.insert(new_row_index.as_usize(), "".grapheme_string());
}
}
pub fn insert_new_line_at_start_of_current_line(args: EditorArgsMut<'_>) {
let EditorArgsMut { buffer, engine } = args;
{
let buffer_mut = buffer.get_mut(engine.viewport());
let cur_row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
buffer_mut
.inner
.lines
.insert(cur_row_index.as_usize(), "".grapheme_string());
}
{
let buffer_mut = buffer.get_mut(engine.viewport());
scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
}
}
pub fn insert_new_line_at_middle_of_current_line(args: EditorArgsMut<'_>) {
let EditorArgsMut { buffer, engine } = args;
if let Some(line) = buffer.line_at_caret_scr_adj() {
let caret_adj = buffer.get_caret_scr_adj();
let col_index = caret_adj.col_index;
let split_result = line.split_at_display_col(col_index);
if let Some((left_string, right_string)) = split_result {
let row_index = caret_adj.row_index.as_usize();
{
let buffer_mut = buffer.get_mut(engine.viewport());
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index],
left_string.grapheme_string(),
);
buffer_mut
.inner
.lines
.insert(row_index + 1, right_string.grapheme_string());
scroll_editor_content::inc_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.row_height,
);
scroll_editor_content::reset_caret_col(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
);
}
}
}
}
}
pub fn delete_at_caret(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
) -> Option<()> {
empty_check_early_return!(buffer, @None);
if buffer.string_at_caret().is_some() {
delete_at_caret_helper::delete_in_middle_of_line(buffer, engine)?;
} else {
delete_at_caret_helper::delete_at_end_of_line(buffer, engine)?;
}
None
}
mod delete_at_caret_helper {
use super::{inline_string, EditorBuffer, EditorEngine, GCString, GCStringExt};
pub fn delete_in_middle_of_line(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
) -> Option<()> {
let line = buffer.line_at_caret_scr_adj()?;
let new_line_content =
line.delete_char_at_col(buffer.get_caret_scr_adj().col_index)?;
{
let buffer_mut = buffer.get_mut(engine.viewport());
let row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index.as_usize()],
new_line_content.grapheme_string(),
);
}
None
}
pub fn delete_at_end_of_line(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
) -> Option<()> {
let this_line = buffer.line_at_caret_scr_adj()?;
let next_line = buffer.next_line_below_caret_to_string()?;
let new_line_content =
inline_string!("{a}{b}", a = this_line.string, b = next_line.string);
{
let buffer_mut = buffer.get_mut(engine.viewport());
let row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index.as_usize()],
new_line_content.grapheme_string(),
);
buffer_mut.inner.lines.remove(row_index.as_usize() + 1);
}
None
}
}
pub fn backspace_at_caret(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
) -> Option<()> {
empty_check_early_return!(buffer, @None);
match buffer.string_to_left_of_caret() {
Some(seg_result) => {
backspace_at_caret_helper::backspace_in_middle_of_line(
buffer,
engine,
seg_result.start_at,
)?;
}
None => {
backspace_at_caret_helper::backspace_at_start_of_line(buffer, engine)?;
}
}
None
}
mod backspace_at_caret_helper {
use super::{caret_scroll_index, inline_string, row, scroll_editor_content, ColIndex,
EditorBuffer, EditorEngine, GCString, GCStringExt};
pub fn backspace_in_middle_of_line(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
delete_at_this_display_col: ColIndex,
) -> Option<()> {
let cur_line = buffer.line_at_caret_scr_adj()?;
let new_line_content = cur_line.delete_char_at_col(delete_at_this_display_col)?;
{
let buffer_mut = buffer.get_mut(engine.viewport());
let cur_row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[cur_row_index.as_usize()],
new_line_content.grapheme_string(),
);
let new_line_content_display_width =
buffer_mut.inner.lines[cur_row_index.as_usize()].display_width;
scroll_editor_content::set_caret_col_to(
delete_at_this_display_col,
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.col_width,
new_line_content_display_width,
);
}
None
}
pub fn backspace_at_start_of_line(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
) -> Option<()> {
let this_line = buffer.line_at_caret_scr_adj()?;
let prev_line = buffer.prev_line_above_caret()?;
let prev_line_display_width = {
let prev_row_index = buffer.get_caret_scr_adj().row_index - row(1);
buffer.get_line_display_width_at_row_index(prev_row_index)
};
let new_line_content =
inline_string!("{a}{b}", a = prev_line.string, b = this_line.string);
{
let buffer_mut = buffer.get_mut(engine.viewport());
let prev_row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index
- row(1);
let cur_row_index =
(*buffer_mut.inner.caret_raw + *buffer_mut.inner.scr_ofs).row_index;
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[prev_row_index.as_usize()],
new_line_content.grapheme_string(),
);
let new_line_content_display_width =
buffer_mut.inner.lines[prev_row_index.as_usize()].display_width;
buffer_mut.inner.lines.remove(cur_row_index.as_usize());
scroll_editor_content::dec_caret_row(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
);
scroll_editor_content::set_caret_col_to(
caret_scroll_index::col_index_for_width(prev_line_display_width),
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
buffer_mut.inner.vp.col_width,
new_line_content_display_width,
);
}
None
}
}
pub fn delete_selected(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
with: DeleteSelectionWith,
) -> Option<()> {
empty_check_early_return!(buffer, @None);
if buffer.get_selection_list().is_empty() {
return None;
}
let selection_map = buffer.get_selection_list().clone();
let (lines_to_remove, lines_to_replace) =
delete_selected_helper::analyze_selections(buffer, &selection_map);
delete_selected_helper::apply_deletions(
buffer,
engine,
lines_to_remove,
&lines_to_replace,
);
delete_selected_helper::restore_caret_and_clear_selection(
buffer,
engine,
&selection_map,
with,
);
None
}
mod delete_selected_helper {
use super::{caret_scroll_index, col, ColIndex, DeleteSelectionWith, EditorBuffer,
EditorEngine, GCString, GCStringExt, HashMap, InlineString, InlineVec,
RowIndex, SelectionRange, Width};
pub fn analyze_selections(
buffer: &EditorBuffer,
selection_map: &crate::SelectionList,
) -> (InlineVec<RowIndex>, HashMap<RowIndex, InlineString>) {
let lines = buffer.get_lines();
let selected_row_indices = selection_map.get_ordered_indices();
let mut vec_row_indices_to_remove = InlineVec::<RowIndex>::new();
let mut map_lines_to_replace = HashMap::new();
for selected_row_index in selected_row_indices {
if let Some(selection_range) = selection_map.get(selected_row_index) {
let line_width =
buffer.get_line_display_width_at_row_index(selected_row_index);
let (start_col_index, end_col_index) = selection_range.as_tuple();
if should_remove_entire_line(start_col_index, end_col_index, line_width) {
vec_row_indices_to_remove.push(selected_row_index);
continue;
}
if selection_range.start() == selection_range.end() {
continue;
}
if let Some(remaining_text) = prepare_partial_line_replacement(
lines,
selected_row_index,
selection_range,
end_col_index,
line_width,
) {
map_lines_to_replace.insert(selected_row_index, remaining_text);
}
}
}
(vec_row_indices_to_remove, map_lines_to_replace)
}
fn should_remove_entire_line(
start_col_index: ColIndex,
end_col_index: ColIndex,
line_width: Width,
) -> bool {
start_col_index == col(0)
&& end_col_index == caret_scroll_index::col_index_for_width(line_width)
}
fn prepare_partial_line_replacement(
lines: &[GCString],
selected_row_index: RowIndex,
selection_range: SelectionRange,
end_col_index: ColIndex,
line_width: Width,
) -> Option<InlineString> {
let line_gcs = lines.get(selected_row_index.as_usize())?.clone();
let keep_before_selected_str = line_gcs.clip(
col(0),
selection_range.get_start_display_col_index_as_width(),
);
let keep_after_selected_str = line_gcs.clip(end_col_index, line_width);
let mut remaining_text = InlineString::with_capacity(
keep_before_selected_str.len() + keep_after_selected_str.len(),
);
remaining_text.push_str(keep_before_selected_str);
remaining_text.push_str(keep_after_selected_str);
Some(remaining_text)
}
pub fn apply_deletions(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
mut lines_to_remove: InlineVec<RowIndex>,
lines_to_replace: &HashMap<RowIndex, InlineString>,
) {
{
let buffer_mut = buffer.get_mut(engine.viewport());
for row_index in lines_to_replace.keys() {
let new_line_content = lines_to_replace[row_index].clone();
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index.as_usize()],
new_line_content.grapheme_string(),
);
}
lines_to_remove.reverse();
for row_index in lines_to_remove {
buffer_mut.inner.lines.remove(row_index.as_usize());
}
}
}
pub fn restore_caret_and_clear_selection(
buffer: &mut EditorBuffer,
engine: &mut EditorEngine,
selection_map: &crate::SelectionList,
with: DeleteSelectionWith,
) {
let maybe_new_caret =
selection_map.get_caret_at_start_of_range_scroll_adjusted(with);
if let Some(new_caret_scr_adj) = maybe_new_caret {
{
let buffer_mut = buffer.get_mut(engine.viewport());
let caret_raw = new_caret_scr_adj + *buffer_mut.inner.scr_ofs;
*buffer_mut.inner.caret_raw = caret_raw;
}
}
buffer.clear_selection();
}
}
fn insert_into_existing_line(
args: EditorArgsMut<'_>,
caret_scr_adj: CaretScrAdj,
chunk: &str,
) -> Option<()> {
let EditorArgsMut { buffer, engine } = args;
let row_index = caret_scr_adj.row_index;
let line = buffer.line_at_row_index(row_index)?;
let (new_line_content, chunk_display_width) =
line.insert_chunk_at_col(caret_scr_adj.col_index, chunk);
{
let buffer_mut = buffer.get_mut(engine.viewport());
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index.as_usize()],
new_line_content.grapheme_string(),
);
let new_line_content_display_width =
buffer_mut.inner.lines[row_index.as_usize()].display_width;
scroll_editor_content::inc_caret_col_by(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
chunk_display_width,
new_line_content_display_width,
buffer_mut.inner.vp.col_width,
);
}
None
}
fn fill_in_missing_lines_up_to_row(args: EditorArgsMut<'_>, row_index: RowIndex) {
let EditorArgsMut { buffer, engine } = args;
let max_row_index = row_index.as_usize();
if buffer.get_lines().get(max_row_index).is_none() {
for row_index in 0..=max_row_index {
if buffer.get_lines().get(row_index).is_none() {
{
let buffer_mut = buffer.get_mut(engine.viewport());
buffer_mut.inner.lines.push("".grapheme_string());
}
}
}
}
}
fn insert_chunk_into_new_line(
args: EditorArgsMut<'_>,
caret_scr_adj: CaretScrAdj,
chunk: &str,
) -> Option<()> {
let EditorArgsMut { buffer, engine } = args;
let row_index_scr_adj = caret_scr_adj.row_index.as_usize();
let _unused = buffer.get_lines().get(row_index_scr_adj)?;
{
let buffer_mut = buffer.get_mut(engine.viewport());
let new_content = chunk.grapheme_string();
let _unused: GCString = std::mem::replace(
&mut buffer_mut.inner.lines[row_index_scr_adj],
new_content,
);
let line_content = &buffer_mut.inner.lines[row_index_scr_adj];
let line_content_display_width = line_content.display_width;
let col_amt = GCString::width(chunk);
scroll_editor_content::inc_caret_col_by(
buffer_mut.inner.caret_raw,
buffer_mut.inner.scr_ofs,
col_amt,
line_content_display_width,
buffer_mut.inner.vp.col_width,
);
}
None
}
#[cfg(test)]
mod tests {
use smallvec::smallvec;
use crate::{assert_eq2, caret_scr_adj, col,
editor::{editor_test_fixtures::{assert, mock_real_objects_for_editor},
sizing::VecEditorContentLines},
editor_engine::engine_internal_api,
row,
system_clipboard_service_provider::clipboard_test_fixtures::TestClipboard,
width, CaretDirection, EditorArgsMut, EditorBuffer, EditorEvent,
GCString, GCStringExt, DEFAULT_SYN_HI_FILE_EXT};
#[test]
fn editor_delete() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::InsertString("abc".into()),
EditorEvent::InsertNewLine,
EditorEvent::InsertString("ab".into()),
EditorEvent::InsertNewLine,
EditorEvent::InsertString("a".into()),
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(1) + row(2)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Left),
EditorEvent::Delete,
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(2)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Up),
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::Delete,
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 2);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(2) + row(1)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Up),
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::Delete,
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 1);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(3) + row(0)));
assert::line_at_caret(&buffer, "abcab");
}
#[test]
fn editor_backspace() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::InsertString("abc".into()),
EditorEvent::InsertNewLine,
EditorEvent::InsertString("ab".into()),
EditorEvent::InsertNewLine,
EditorEvent::InsertString("a".into()),
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(1) + row(2)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::Backspace],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(2)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::Backspace],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(2) + row(1)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Left),
EditorEvent::MoveCaret(CaretDirection::Left),
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(1)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::Backspace],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 1);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(3) + row(0)));
assert::line_at_caret(&buffer, "abcab");
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::InsertString("😃".into()),
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(7) + row(0)));
EditorEvent::apply_editor_event(
&mut engine,
&mut buffer,
EditorEvent::Backspace,
&mut TestClipboard::default(),
);
assert::line_at_caret(&buffer, "abcab");
}
#[test]
fn editor_insert_new_line() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
assert_eq2!(buffer.get_lines().len(), 1);
assert_eq2!(buffer.get_lines().len(), 1);
assert::none_is_at_caret(&buffer);
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertChar('a')],
&mut TestClipboard::default(),
);
assert::none_is_at_caret(&buffer);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(1) + row(0)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertNewLine],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 2);
assert::none_is_at_caret(&buffer);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(1)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertChar('a')],
&mut TestClipboard::default(),
);
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::MoveCaret(CaretDirection::Left)],
&mut TestClipboard::default(),
);
assert::str_is_at_caret(&buffer, "a");
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertNewLine],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 3);
assert::str_is_at_caret(&buffer, "a");
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(2)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::InsertChar('b'),
],
&mut TestClipboard::default(),
);
assert::none_is_at_caret(&buffer);
assert_eq2!(
engine_internal_api::line_at_caret_to_string(&buffer,).unwrap(),
&"ab".grapheme_string()
);
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Left),
EditorEvent::InsertNewLine,
],
&mut TestClipboard::default(),
);
assert::str_is_at_caret(&buffer, "b");
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(3)));
assert_eq2!(buffer.get_lines().len(), 4);
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::MoveCaret(CaretDirection::Up),
EditorEvent::MoveCaret(CaretDirection::Right),
EditorEvent::InsertNewLine,
],
&mut TestClipboard::default(),
);
assert_eq2!(buffer.get_lines().len(), 5);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(3)));
}
#[test]
fn editor_insertion() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(0)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertChar('a')],
&mut TestClipboard::default(),
);
let expected: VecEditorContentLines = smallvec!["a".grapheme_string()];
assert_eq2!(*buffer.get_lines(), expected);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(1) + row(0)));
engine_internal_api::insert_new_line_at_caret(EditorArgsMut {
buffer: &mut buffer,
engine: &mut engine,
});
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertChar('b')],
&mut TestClipboard::default(),
);
let expected: VecEditorContentLines =
smallvec!["a".grapheme_string(), "b".grapheme_string()];
assert_eq2!(*buffer.get_lines(), expected);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(1) + row(1)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![
EditorEvent::InsertNewLine,
EditorEvent::InsertNewLine,
EditorEvent::InsertChar('😀'),
],
&mut TestClipboard::default(),
);
let expected: VecEditorContentLines = smallvec![
"a".grapheme_string(),
"b".grapheme_string(),
"".grapheme_string(),
"😀".grapheme_string()
];
assert_eq2!(*buffer.get_lines(), expected);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(2) + row(3)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertChar('d')],
&mut TestClipboard::default(),
);
let expected: VecEditorContentLines = smallvec![
"a".grapheme_string(),
"b".grapheme_string(),
"".grapheme_string(),
"😀d".grapheme_string()
];
assert_eq2!(*buffer.get_lines(), expected);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(3) + row(3)));
EditorEvent::apply_editor_events::<(), ()>(
&mut engine,
&mut buffer,
vec![EditorEvent::InsertString("🙏🏽".into())],
&mut TestClipboard::default(),
);
assert_eq2!(width(2), GCString::width("🙏🏽"));
let expected: VecEditorContentLines = smallvec![
"a".grapheme_string(),
"b".grapheme_string(),
"".grapheme_string(),
"😀d🙏🏽".grapheme_string()
];
assert_eq2!(*buffer.get_lines(), expected);
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(5) + row(3)));
}
#[test]
fn test_insert_lines_batch_at_caret_basic() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
let lines = vec!["line1", "line2", "line3"];
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine, buffer: &mut buffer },
&lines,
);
assert_eq2!(buffer.get_lines().len(), 3);
assert_eq2!(buffer.get_lines()[0], "line1".grapheme_string());
assert_eq2!(buffer.get_lines()[1], "line2".grapheme_string());
assert_eq2!(buffer.get_lines()[2], "line3".grapheme_string());
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(5) + row(2)));
}
#[test]
fn test_insert_lines_batch_with_empty_lines() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
let lines = vec!["line1", "", "line3"];
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine, buffer: &mut buffer },
&lines,
);
assert_eq2!(buffer.get_lines().len(), 3);
assert_eq2!(buffer.get_lines()[0], "line1".grapheme_string());
assert_eq2!(buffer.get_lines()[1], "".grapheme_string());
assert_eq2!(buffer.get_lines()[2], "line3".grapheme_string());
}
#[test]
fn test_insert_lines_batch_at_middle_of_line() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
buffer.init_with(vec!["existing content".to_string()]);
let buffer_mut = buffer.get_mut(engine.viewport());
buffer_mut.inner.caret_raw.col_index = col(8); drop(buffer_mut);
let lines = vec!["NEW1", "NEW2"];
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine, buffer: &mut buffer },
&lines,
);
assert_eq2!(buffer.get_lines().len(), 2);
let lines = buffer.get_lines();
if !lines.is_empty() {
assert_eq2!(lines[0], "existingNEW1".grapheme_string());
}
if lines.len() >= 2 {
assert_eq2!(lines[1], "NEW2 content".grapheme_string());
}
}
#[test]
fn test_batch_vs_individual_insert_result_equivalence() {
let mut buffer1 = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine1 = mock_real_objects_for_editor::make_editor_engine();
let mut buffer2 = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine2 = mock_real_objects_for_editor::make_editor_engine();
let lines = vec!["first", "second", "third"];
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine1, buffer: &mut buffer1 },
&lines,
);
engine_internal_api::insert_str_at_caret(
EditorArgsMut { engine: &mut engine2, buffer: &mut buffer2 },
"first",
);
engine_internal_api::insert_new_line_at_caret(EditorArgsMut {
engine: &mut engine2,
buffer: &mut buffer2,
});
engine_internal_api::insert_str_at_caret(
EditorArgsMut { engine: &mut engine2, buffer: &mut buffer2 },
"second",
);
engine_internal_api::insert_new_line_at_caret(EditorArgsMut {
engine: &mut engine2,
buffer: &mut buffer2,
});
engine_internal_api::insert_str_at_caret(
EditorArgsMut { engine: &mut engine2, buffer: &mut buffer2 },
"third",
);
assert_eq2!(buffer1.get_lines(), buffer2.get_lines());
assert_eq2!(buffer1.get_caret_scr_adj(), buffer2.get_caret_scr_adj());
}
#[test]
fn test_insert_lines_batch_empty_vector() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
let lines: Vec<&str> = vec![];
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine, buffer: &mut buffer },
&lines,
);
assert_eq2!(buffer.get_lines().len(), 1);
assert_eq2!(buffer.get_lines()[0], "".grapheme_string());
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(0) + row(0)));
}
#[test]
fn test_insert_lines_batch_large_content() {
let mut buffer = EditorBuffer::new_empty(Some(DEFAULT_SYN_HI_FILE_EXT), None);
let mut engine = mock_real_objects_for_editor::make_editor_engine();
let lines: Vec<String> = (0..100).map(|i| format!("Line number {i}")).collect();
let lines_refs: Vec<&str> = lines.iter().map(String::as_str).collect();
engine_internal_api::insert_str_batch_at_caret(
EditorArgsMut { engine: &mut engine, buffer: &mut buffer },
&lines_refs,
);
assert_eq2!(buffer.get_lines().len(), 100);
assert_eq2!(buffer.get_lines()[0], "Line number 0".grapheme_string());
assert_eq2!(buffer.get_lines()[99], "Line number 99".grapheme_string());
let last_line_len = "Line number 99".len();
assert_eq2!(buffer.get_caret_scr_adj(), caret_scr_adj(col(last_line_len) + row(99)));
}
}