handle_new_char tests, bug fixes for #869

This commit is contained in:
Anton-4 2021-01-13 16:14:11 +01:00
parent 29e9cf3a6f
commit d6dc6dbf8b
4 changed files with 295 additions and 34 deletions

View file

@ -79,7 +79,7 @@ fn run_event_loop(file_path_opt: Option<&Path>) -> Result<(), Box<dyn Error>> {
let event_loop = winit::event_loop::EventLoop::new();
let window = winit::window::WindowBuilder::new()
.with_inner_size(PhysicalSize::new(1000.0, 800.0))
.with_inner_size(PhysicalSize::new(1200.0, 1000.0))
.build(&event_loop)
.unwrap();
@ -384,11 +384,11 @@ fn queue_all_text(
};
let caret_pos_label = Text {
position: (30.0, (size.height as f32) - 45.0).into(),
position: ((size.width as f32) - 150.0, (size.height as f32) - 40.0).into(),
area_bounds,
color: TXT_COLOR.into(),
text: format!("Ln {}, Col {}", caret_pos.line, caret_pos.column),
size: 30.0,
size: 25.0,
..Default::default()
};

View file

@ -2,6 +2,7 @@ use super::ed_model::{Position, RawSelection};
use crate::text_buffer::TextBuffer;
use crate::util::is_newline;
use super::app_model::AppModel;
use super::ed_model::EdModel;
use crate::error::EdResult;
use std::cmp::{max, min};
@ -298,9 +299,15 @@ pub fn move_caret_down(
(new_caret_pos, new_selection_opt)
}
fn del_selection(selection: RawSelection, ed_model: &mut EdModel) -> EdResult<()> {
ed_model.text_buf.del_selection(selection)?;
ed_model.caret_pos = selection.start_pos;
Ok(())
}
pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResult<()> {
if let Some(ref mut ed_model) = app_model.ed_model_opt {
ed_model.selection_opt = None;
let old_caret_pos = ed_model.caret_pos;
match received_char {
@ -308,34 +315,46 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu
// On Linux, '\u{8}' is backspace,
// on macOS '\u{7f}'.
if let Some(selection) = ed_model.selection_opt {
ed_model.text_buf.del_selection(selection)?;
ed_model.caret_pos = selection.start_pos;
del_selection(selection, ed_model)?;
} else {
ed_model.text_buf.pop_char(old_caret_pos);
ed_model.caret_pos =
move_caret_left(old_caret_pos, None, false, &ed_model.text_buf).0;
ed_model.text_buf.pop_char(old_caret_pos);
}
}
ch if is_newline(ch) => {
ed_model.text_buf.insert_char(old_caret_pos, ch)?;
if let Some(selection) = ed_model.selection_opt {
del_selection(selection, ed_model)?;
ed_model.text_buf.insert_char(ed_model.caret_pos, &'\n')?;
} else {
ed_model.text_buf.insert_char(old_caret_pos, &'\n')?;
ed_model.caret_pos = Position {
line: old_caret_pos.line + 1,
column: 0,
};
}
}
'\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}' => {
// These are private use characters; ignore them.
// See http://www.unicode.org/faq/private_use.html
}
_ => {
if let Some(selection) = ed_model.selection_opt {
del_selection(selection, ed_model)?;
ed_model.text_buf.insert_char(ed_model.caret_pos, received_char)?;
ed_model.caret_pos =
move_caret_right(ed_model.caret_pos, None, false, &ed_model.text_buf).0;
} else {
ed_model.text_buf.insert_char(old_caret_pos, received_char)?;
ed_model.caret_pos = Position {
line: old_caret_pos.line,
column: old_caret_pos.column + 1,
};
}
}
}
@ -344,3 +363,240 @@ pub fn handle_new_char(app_model: &mut AppModel, received_char: &char) -> EdResu
Ok(())
}
#[cfg(test)]
mod test_update {
use crate::selection::test_selection::{convert_dsl_to_selection, convert_selection_to_dsl, text_buffer_from_dsl_str, all_lines_vec};
use crate::mvc::ed_model::{Position, EdModel, RawSelection};
use crate::mvc::app_model::AppModel;
use crate::mvc::update::{handle_new_char};
use crate::text_buffer::TextBuffer;
fn gen_caret_text_buf(lines: &[&str]) -> Result<(Position, Option<RawSelection>, TextBuffer), String> {
let lines_string_slice: Vec<String> = lines.iter().map(|l| l.to_string()).collect();
let (selection_opt, caret_pos) = convert_dsl_to_selection(&lines_string_slice)?;
let text_buf = text_buffer_from_dsl_str(&lines_string_slice);
Ok((caret_pos, selection_opt, text_buf))
}
fn mock_app_model(text_buf: TextBuffer, caret_pos: Position, selection_opt: Option<RawSelection>) -> AppModel {
AppModel {
ed_model_opt: Some(
EdModel{
text_buf,
caret_pos,
selection_opt,
glyph_dim_rect_opt: None,
has_focus: true
}
)
}
}
fn assert_insert(
pre_lines_str: &[&str],
expected_post_lines_str: &[&str],
new_char: char,
) -> Result<(), String> {
let (caret_pos, selection_opt, pre_text_buf) = gen_caret_text_buf(pre_lines_str)?;
let mut app_model = mock_app_model(pre_text_buf, caret_pos, selection_opt);
if let Err(e) = handle_new_char(&mut app_model, &new_char) {
return Err(e.to_string());
}
if let Some(ed_model) = app_model.ed_model_opt {
let mut actual_lines = all_lines_vec(&ed_model.text_buf);
let dsl_slice = convert_selection_to_dsl(ed_model.selection_opt, ed_model.caret_pos, &mut actual_lines).unwrap();
assert_eq!(dsl_slice, expected_post_lines_str);
} else {
panic!("Mock AppModel did not have an EdModel.");
}
Ok(())
}
#[test]
fn insert_new_char_simple() -> Result<(), String> {
assert_insert(
&["|"], &["a|"], 'a'
)?;
assert_insert(
&["|"], &[" |"], ' '
)?;
assert_insert(
&["a|"], &["aa|"], 'a'
)?;
assert_insert(
&["a|"], &["a |"], ' '
)?;
assert_insert(
&["a|\n", ""], &["ab|\n", ""], 'b'
)?;
assert_insert(
&["a|\n", ""], &["ab|\n", ""], 'b'
)?;
assert_insert(
&["a\n", "|"], &["a\n", "b|"], 'b'
)?;
assert_insert(
&["a\n", "b\n", "c|"], &["a\n", "b\n", "cd|"], 'd'
)?;
Ok(())
}
#[test]
fn insert_new_char_mid() -> Result<(), String> {
assert_insert(
&["ab|d"], &["abc|d"], 'c'
)?;
assert_insert(
&["a|cd"], &["ab|cd"], 'b'
)?;
assert_insert(
&["abc\n", "|e"], &["abc\n", "d|e"], 'd'
)?;
assert_insert(
&["abc\n", "def\n", "| "], &["abc\n", "def\n", "g| "], 'g'
)?;
assert_insert(
&["abc\n", "def\n", "| "], &["abc\n", "def\n", " | "], ' '
)?;
Ok(())
}
#[test]
fn simple_backspace() -> Result<(), String> {
assert_insert(
&["|"], &["|"], '\u{8}'
)?;
assert_insert(
&[" |"], &["|"], '\u{8}'
)?;
assert_insert(
&["a|"], &["|"], '\u{8}'
)?;
assert_insert(
&["ab|"], &["a|"], '\u{8}'
)?;
assert_insert(
&["a|\n", ""], &["|\n", ""], '\u{8}'
)?;
assert_insert(
&["ab|\n", ""], &["a|\n", ""], '\u{8}'
)?;
assert_insert(
&["a\n", "|"], &["a|"], '\u{8}'
)?;
assert_insert(
&["a\n", "b\n", "c|"], &["a\n", "b\n", "|"], '\u{8}'
)?;
assert_insert(
&["a\n", "b\n", "|"], &["a\n", "b|"], '\u{8}'
)?;
Ok(())
}
#[test]
fn selection_backspace() -> Result<(), String> {
assert_insert(
&["[a]|"], &["|"], '\u{8}'
)?;
assert_insert(
&["a[a]|"], &["a|"], '\u{8}'
)?;
assert_insert(
&["[aa]|"], &["|"], '\u{8}'
)?;
assert_insert(
&["a[b c]|"], &["a|"], '\u{8}'
)?;
assert_insert(
&["[abc]|\n", ""], &["|\n", ""], '\u{8}'
)?;
assert_insert(
&["a\n", "[abc]|"], &["a\n","|"], '\u{8}'
)?;
assert_insert(
&["[a\n", "abc]|"], &["|"], '\u{8}'
)?;
assert_insert(
&["a[b\n", "cdef ghij]|"], &["a|"], '\u{8}'
)?;
assert_insert(
&["[a\n", "b\n", "c]|"], &["|"], '\u{8}'
)?;
assert_insert(
&["a\n", "[b\n", "]|"], &["a\n", "|"], '\u{8}'
)?;
assert_insert(
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "d|\n", "jkl"], '\u{8}'
)?;
assert_insert(
&["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "|\n", "jkl"], '\u{8}'
)?;
assert_insert(
&["abc\n", "\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "\n", "|\n", "jkl"], '\u{8}'
)?;
assert_insert(
&["[abc\n", "\n", "def\n", "ghi\n", "jkl]|"], &["|"], '\u{8}'
)?;
Ok(())
}
#[test]
fn insert_with_selection() -> Result<(), String> {
assert_insert(
&["[a]|"], &["z|"], 'z'
)?;
assert_insert(
&["a[a]|"], &["az|"], 'z'
)?;
assert_insert(
&["[aa]|"], &["z|"], 'z'
)?;
assert_insert(
&["a[b c]|"], &["az|"], 'z'
)?;
assert_insert(
&["[abc]|\n", ""], &["z|\n", ""], 'z'
)?;
assert_insert(
&["a\n", "[abc]|"], &["a\n","z|"], 'z'
)?;
assert_insert(
&["[a\n", "abc]|"], &["z|"], 'z'
)?;
assert_insert(
&["a[b\n", "cdef ghij]|"], &["az|"], 'z'
)?;
assert_insert(
&["[a\n", "b\n", "c]|"], &["z|"], 'z'
)?;
assert_insert(
&["a\n", "[b\n", "]|"], &["a\n", "z|"], 'z'
)?;
assert_insert(
&["abc\n", "d[ef\n", "ghi]|\n", "jkl"], &["abc\n", "dz|\n", "jkl"], 'z'
)?;
assert_insert(
&["abc\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "z|\n", "jkl"], 'z'
)?;
assert_insert(
&["abc\n", "\n", "[def\n", "ghi]|\n", "jkl"], &["abc\n", "\n", "z|\n", "jkl"], 'z'
)?;
assert_insert(
&["[abc\n", "\n", "def\n", "ghi\n", "jkl]|"], &["z|"], 'z'
)?;
Ok(())
}
}

View file

@ -123,7 +123,7 @@ pub fn create_selection_rects<'a>(
}
#[cfg(test)]
mod test_parse {
pub mod test_selection {
use crate::error::{EdResult, OutOfBounds};
use crate::mvc::ed_model::{Position, RawSelection};
use crate::mvc::update::{move_caret_down, move_caret_left, move_caret_right, move_caret_up, MoveCaretFun};
@ -141,7 +141,7 @@ mod test_parse {
pub struct LineParser;
// show selection and caret position as symbols in lines for easy testing
fn convert_selection_to_dsl(
pub fn convert_selection_to_dsl(
raw_sel_opt: Option<RawSelection>,
caret_pos: Position,
lines: &mut [String],
@ -217,6 +217,16 @@ mod test_parse {
}
}
pub fn text_buffer_from_dsl_str(lines: &[String]) -> TextBuffer {
text_buffer_from_str(
&lines
.iter()
.map(|line| line.replace(&['[', ']', '|'][..], ""))
.collect::<Vec<String>>()
.join("")
)
}
pub fn all_lines_vec(text_buf: &TextBuffer) -> Vec<String> {
let mut lines: Vec<String> = Vec::new();
@ -235,7 +245,7 @@ mod test_parse {
}
// Retrieve selection and position from formatted string
fn convert_dsl_to_selection(
pub fn convert_dsl_to_selection(
lines: &[String],
) -> Result<(Option<RawSelection>, Position), String> {
let lines_str: String = lines.join("");
@ -349,13 +359,7 @@ mod test_parse {
let (sel_opt, caret_pos) = convert_dsl_to_selection(&pre_lines)?;
let clean_text_buf =
text_buffer_from_str(
&pre_lines
.into_iter()
.map(|line| line.replace(&['[', ']', '|'][..], ""))
.collect::<Vec<String>>()
.join("")
);
text_buffer_from_dsl_str(&pre_lines);
let (new_caret_pos, new_sel_opt) =
move_fun(caret_pos, sel_opt, shift_pressed, &clean_text_buf);

View file

@ -23,7 +23,7 @@ impl TextBuffer {
pub fn insert_char(&mut self, caret_pos: Position, new_char: &char) -> EdResult<()> {
let char_indx = self.pos_to_char_indx(caret_pos);
ensure!(char_indx > self.text_rope.len_chars(), OutOfBounds{
ensure!(char_indx <= self.text_rope.len_chars(), OutOfBounds{
index: char_indx,
collection_name: "Rope",
len: self.text_rope.len_chars()
@ -35,16 +35,17 @@ impl TextBuffer {
}
pub fn pop_char(&mut self, caret_pos: Position) {
let char_indx = self.pos_to_char_indx(caret_pos) - 1;
if char_indx < self.text_rope.len_chars() {
self.text_rope.remove(char_indx..char_indx);
let char_indx = self.pos_to_char_indx(caret_pos);
if (char_indx > 0) && char_indx <= self.text_rope.len_chars() {
self.text_rope.remove((char_indx - 1)..char_indx);
}
}
pub fn del_selection(&mut self, raw_sel: RawSelection) -> EdResult<()> {
let (start_char_indx, end_char_indx) = self.sel_to_tup(raw_sel)?;
ensure!(end_char_indx < self.text_rope.len_chars(), OutOfBounds{
ensure!(end_char_indx <= self.text_rope.len_chars(), OutOfBounds{
index: end_char_indx,
collection_name: "Rope",
len: self.text_rope.len_chars()
@ -102,7 +103,7 @@ impl TextBuffer {
fn sel_to_tup(&self, raw_sel: RawSelection) -> EdResult<(usize, usize)> {
let valid_sel = validate_selection(raw_sel)?;
let start_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos);
let end_char_indx = self.pos_to_char_indx(valid_sel.selection.start_pos);
let end_char_indx = self.pos_to_char_indx(valid_sel.selection.end_pos);
Ok((start_char_indx, end_char_indx))
}