diff --git a/src/buffer/line.rs b/src/buffer/line.rs index d2946fe..866cf12 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -162,6 +162,29 @@ impl Line { } + /// Appends `text` to the end of line, just before the line ending (if + /// any). + /// NOTE: panics if it encounters a line ending in the text. + pub fn append_text(&mut self, text: &str) { + let mut i = self.text.len(); + + // Grow data size + self.text.grow(text.len(), 0); + + // Copy new bytes in + for g in text.graphemes(true) { + if is_line_ending(g) { + panic!("Line::append_text(): line ending in inserted text."); + } + + for b in g.bytes() { + self.text[i] = b; + i += 1 + } + } + } + + /// Remove the text between grapheme positions 'pos_a' and 'pos_b'. pub fn remove_text(&mut self, pos_a: uint, pos_b: uint) { // Bounds checks @@ -463,6 +486,28 @@ fn text_line_insert_text() { assert!(tl.ending == LineEnding::CRLF); } +#[test] +fn text_line_append_text() { + let mut tl = Line::new_from_str("Hello\r\n"); + + tl.append_text(" world!"); + + assert!(tl.text.len() == 12); + assert!(tl.text[0] == ('H' as u8)); + assert!(tl.text[1] == ('e' as u8)); + assert!(tl.text[2] == ('l' as u8)); + assert!(tl.text[3] == ('l' as u8)); + assert!(tl.text[4] == ('o' as u8)); + assert!(tl.text[5] == (' ' as u8)); + assert!(tl.text[6] == ('w' as u8)); + assert!(tl.text[7] == ('o' as u8)); + assert!(tl.text[8] == ('r' as u8)); + assert!(tl.text[9] == ('l' as u8)); + assert!(tl.text[10] == ('d' as u8)); + assert!(tl.text[11] == ('!' as u8)); + assert!(tl.ending == LineEnding::CRLF); +} + #[test] fn text_line_remove_text() { let mut tl = Line::new_from_str("Hello world!\r\n"); diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index bf44c7e..0978735 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -150,7 +150,9 @@ impl Buffer { } // All other cases else { - self.root.remove_text_recursive(pos_a, pos_b); + if self.root.remove_text_recursive(pos_a, pos_b) { + panic!("Buffer::remove_text(): dangling left side remains. This should never happen!"); + } self.root.set_last_line_ending_recursive(); } } @@ -593,6 +595,120 @@ fn insert_text_in_non_empty_buffer_7() { } +#[test] +fn remove_text_1() { + let mut buf = Buffer::new(); + + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); + assert!(buf.len() == 29); + assert!(buf.root.line_count == 6); + + buf.remove_text(0, 3); + + let mut iter = buf.grapheme_iter(); + + assert!(buf.len() == 26); + assert!(buf.root.line_count == 5); + assert!(Some("t") == iter.next()); + assert!(Some("h") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("r") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("p") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(Some("p") == iter.next()); + assert!(Some("l") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(Some("f") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("t") == iter.next()); + assert!(Some("h") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("w") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(Some("r") == iter.next()); + assert!(Some("l") == iter.next()); + assert!(Some("d") == iter.next()); + assert!(Some("!") == iter.next()); + assert!(None == iter.next()); +} + + +#[test] +fn remove_text_2() { + let mut buf = Buffer::new(); + + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); + assert!(buf.len() == 29); + assert!(buf.root.line_count == 6); + + buf.remove_text(6, 18); + + let mut iter = buf.grapheme_iter(); + + assert!(buf.len() == 17); + assert!(buf.root.line_count == 4); + assert!(Some("H") == iter.next()); + assert!(Some("i") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("t") == iter.next()); + assert!(Some("h") == iter.next()); + assert!(Some("f") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("t") == iter.next()); + assert!(Some("h") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("w") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(Some("r") == iter.next()); + assert!(Some("l") == iter.next()); + assert!(Some("d") == iter.next()); + assert!(Some("!") == iter.next()); + assert!(None == iter.next()); +} + + +#[test] +fn remove_text_3() { + let mut buf = Buffer::new(); + + buf.insert_text("Hi\nthere\npeople\nof\nthe\nworld!", 0); + assert!(buf.len() == 29); + assert!(buf.root.line_count == 6); + + buf.remove_text(17, 29); + + let mut iter = buf.grapheme_iter(); + + assert!(buf.len() == 17); + assert!(buf.root.line_count == 4); + assert!(Some("H") == iter.next()); + assert!(Some("i") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("t") == iter.next()); + assert!(Some("h") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("r") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("p") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(Some("p") == iter.next()); + assert!(Some("l") == iter.next()); + assert!(Some("e") == iter.next()); + assert!(Some("\n") == iter.next()); + assert!(Some("o") == iter.next()); + assert!(None == iter.next()); +} + + #[test] fn remove_lines_1() { let mut buf = Buffer::new(); diff --git a/src/buffer/node.rs b/src/buffer/node.rs index 666d9a6..60e1cb7 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -317,15 +317,18 @@ impl BufferNode { } - /// Removes text between grapheme positions pos_a and pos_b - pub fn remove_text_recursive(&mut self, pos_a: uint, pos_b: uint) { + /// Removes text between grapheme positions pos_a and pos_b. + /// Returns true if a dangling left side remains from the removal. + /// Returns false otherwise. + pub fn remove_text_recursive(&mut self, pos_a: uint, pos_b: uint) -> bool { let mut temp_node = BufferNode::new(); let mut total_side_removal = false; + let mut dangling_line = false; + let mut do_merge_fix = false; + let mut merge_line_number: uint = 0; match self.data { BufferNodeData::Branch(ref mut left, ref mut right) => { - let mut right_line: Option = None; - // Check for complete removal of both sides, which // should never happen here if pos_a == 0 && pos_b == self.grapheme_count { @@ -347,38 +350,50 @@ impl BufferNode { if pos_a < left.grapheme_count { let a = pos_a; let b = left.grapheme_count; - left.remove_text_recursive(a, b); + dangling_line = left.remove_text_recursive(a, b); } mem::swap(&mut temp_node, &mut (**left)); } - // Partial removal of only left side - else if pos_b < left.grapheme_count { - left.remove_text_recursive(pos_a, pos_b); - } - // Partial removal of only right side - else if pos_a > left.grapheme_count { - right.remove_text_recursive(pos_a - left.grapheme_count, pos_b - left.grapheme_count); - } - // Partial removal of both sides + // Partial removal of one or both sides else { - // TODO + // Right side + if pos_b > left.grapheme_count { + let a = if pos_a > left.grapheme_count {pos_a - left.grapheme_count} else {0}; + let b = pos_b - left.grapheme_count; + right.remove_text_recursive(a, b); + } + + // Left side + if pos_a < left.grapheme_count { + let a = pos_a; + let b = min(pos_b, left.grapheme_count); + do_merge_fix = left.remove_text_recursive(a, b); + merge_line_number = left.line_count - 1; + } } }, BufferNodeData::Leaf(ref mut line) => { - // TODO + line.remove_text(pos_a, pos_b); + dangling_line = line.ending == LineEnding::None; }, } + // Do the merge fix if necessary + if do_merge_fix { + self.merge_line_with_next_recursive(merge_line_number, None); + } // If one of the sides was completely removed, replace self with the // remaining side. - if total_side_removal { + else if total_side_removal { mem::swap(&mut temp_node, self); } self.update_stats(); self.rebalance(); + + return dangling_line; } @@ -436,6 +451,89 @@ impl BufferNode { } + pub fn merge_line_with_next_recursive(&mut self, line_number: uint, fetched_line: Option<&Line>) { + match fetched_line { + None => { + let mut line: Option = self.pull_out_line_recursive(line_number + 1); + if let Some(ref l) = line { + self.merge_line_with_next_recursive(line_number, Some(l)); + } + }, + + Some(line) => { + match self.data { + BufferNodeData::Branch(ref mut left, ref mut right) => { + if line_number < left.line_count { + left.merge_line_with_next_recursive(line_number, Some(line)); + } + else { + right.merge_line_with_next_recursive(line_number - left.line_count, Some(line)); + } + }, + + BufferNodeData::Leaf(ref mut line2) => { + line2.append_text(line.as_str()); + line2.ending = line.ending; + } + } + } + } + + self.update_stats(); + self.rebalance(); + } + + + /// Removes a single line out of the text and returns it. + pub fn pull_out_line_recursive(&mut self, line_number: uint) -> Option { + let mut pulled_line = Line::new(); + let mut temp_node = BufferNode::new(); + let mut side_removal = false; + + match self.data { + BufferNodeData::Branch(ref mut left, ref mut right) => { + if line_number < left.line_count { + if let BufferNodeData::Leaf(ref mut line) = left.data { + mem::swap(&mut pulled_line, line); + mem::swap(&mut temp_node, &mut (**right)); + side_removal = true; + } + else { + pulled_line = left.pull_out_line_recursive(line_number).unwrap(); + } + } + else if line_number < self.line_count { + if let BufferNodeData::Leaf(ref mut line) = right.data { + mem::swap(&mut pulled_line, line); + mem::swap(&mut temp_node, &mut (**left)); + side_removal = true; + } + else { + pulled_line = right.pull_out_line_recursive(line_number - left.line_count).unwrap(); + } + } + else { + return None; + } + }, + + + BufferNodeData::Leaf(ref mut line) => { + panic!("pull_out_line_recursive(): inside leaf node. This should never happen!"); + }, + } + + if side_removal { + mem::swap(&mut temp_node, self); + } + + self.update_stats(); + self.rebalance(); + + return Some(pulled_line); + } + + /// Ensures that the last line in the node tree has no /// ending line break. pub fn set_last_line_ending_recursive(&mut self) { diff --git a/src/string_utils.rs b/src/string_utils.rs index ed18391..8a631da 100644 --- a/src/string_utils.rs +++ b/src/string_utils.rs @@ -87,5 +87,5 @@ pub fn grapheme_pos_to_byte_pos(text: &str, pos: uint) -> uint { return text.len(); } - panic!("grapheme_pos_to_byte_pos(): char position off the end of the string."); + panic!("grapheme_pos_to_byte_pos(): grapheme position off the end of the string."); } \ No newline at end of file