From cb5b79ec9c90395fff267fe7d2e8738e981c8f4d Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Wed, 31 Dec 2014 20:19:12 -0800 Subject: [PATCH] Changed editor drawing to use line iterators. This makes the code easier to follow, and will make it easier to accomodate non-uniform width graphenes later on. --- src/buffer/line.rs | 8 +++ src/buffer/mod.rs | 58 ++++++++++++++++++++- src/buffer/node.rs | 96 ++++++++++++++++++++++++++++++++-- src/term_ui.rs | 126 +++++++++++++++++---------------------------- todo.md | 8 --- 5 files changed, 205 insertions(+), 91 deletions(-) diff --git a/src/buffer/line.rs b/src/buffer/line.rs index 866cf12..cae9073 100644 --- a/src/buffer/line.rs +++ b/src/buffer/line.rs @@ -361,6 +361,14 @@ pub struct LineGraphemeIter<'a> { done: bool, } +impl<'a> LineGraphemeIter<'a> { + pub fn skip_graphemes(&mut self, n: uint) { + for _ in range(0, n) { + self.next(); + } + } +} + impl<'a> Iterator<&'a str> for LineGraphemeIter<'a> { fn next(&mut self) -> Option<&'a str> { if self.done { diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index fc73186..9dc6d91 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -2,7 +2,7 @@ use std::mem; -use self::node::{BufferNode, BufferNodeGraphemeIter}; +use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter}; use self::line::{Line}; use string_utils::{is_line_ending}; @@ -139,6 +139,20 @@ impl Buffer { } } + + pub fn line_iter<'a>(&'a self) -> BufferLineIter<'a> { + BufferLineIter { + li: self.root.line_iter() + } + } + + + pub fn line_iter_at_index<'a>(&'a self, index: uint) -> BufferLineIter<'a> { + BufferLineIter { + li: self.root.line_iter_at_index(index) + } + } + } @@ -197,6 +211,20 @@ impl<'a> Iterator<&'a str> for BufferGraphemeIter<'a> { } +pub struct BufferLineIter<'a> { + li: BufferNodeLineIter<'a>, +} + + +impl<'a> Iterator<&'a Line> for BufferLineIter<'a> { + fn next(&mut self) -> Option<&'a Line> { + self.li.next() + } +} + + + + //================================================================ // TESTS @@ -780,6 +808,33 @@ fn remove_text_10() { } +#[test] +fn remove_text_11() { + let mut buf = Buffer::new(); + + buf.insert_text("1234567890", 0); + assert!(buf.len() == 10); + assert!(buf.root.line_count == 1); + + buf.remove_text(9, 10); + + let mut iter = buf.grapheme_iter(); + + assert!(buf.len() == 9); + assert!(buf.root.line_count == 1); + assert!(Some("1") == iter.next()); + assert!(Some("2") == iter.next()); + assert!(Some("3") == iter.next()); + assert!(Some("4") == iter.next()); + assert!(Some("5") == iter.next()); + assert!(Some("6") == iter.next()); + assert!(Some("7") == iter.next()); + assert!(Some("8") == iter.next()); + assert!(Some("9") == iter.next()); + assert!(None == iter.next()); +} + + #[test] fn remove_lines_1() { let mut buf = Buffer::new(); @@ -966,4 +1021,3 @@ fn grapheme_iter_at_index_2() { - diff --git a/src/buffer/node.rs b/src/buffer/node.rs index c9fbf3c..8237e6b 100644 --- a/src/buffer/node.rs +++ b/src/buffer/node.rs @@ -412,8 +412,6 @@ impl BufferNode { total_side_removal = true; mem::swap(&mut temp_node, &mut (**left)); } - - } // Partial removal of one or both sides else { @@ -443,7 +441,7 @@ impl BufferNode { } line.remove_text(pos_a, pos_b2); - dangling_line = line.ending == LineEnding::None; + dangling_line = line.ending == LineEnding::None && !is_last; }, } @@ -679,6 +677,65 @@ impl BufferNode { } } } + + + /// Creates a line iterator starting at the first line + pub fn line_iter<'a>(&'a self) -> BufferNodeLineIter<'a> { + let mut node_stack: Vec<&'a BufferNode> = Vec::new(); + let mut cur_node = self; + + loop { + match cur_node.data { + BufferNodeData::Leaf(_) => { + break; + }, + + BufferNodeData::Branch(ref left, ref right) => { + node_stack.push(&(**right)); + cur_node = &(**left); + } + } + } + + node_stack.push(cur_node); + + BufferNodeLineIter { + node_stack: node_stack, + } + } + + + /// Creates a line iterator starting at the given line index + pub fn line_iter_at_index<'a>(&'a self, index: uint) -> BufferNodeLineIter<'a> { + let mut node_stack: Vec<&'a BufferNode> = Vec::new(); + let mut cur_node = self; + let mut line_i = index; + + loop { + match cur_node.data { + BufferNodeData::Leaf(_) => { + break; + }, + + BufferNodeData::Branch(ref left, ref right) => { + if line_i < left.line_count { + node_stack.push(&(**right)); + cur_node = &(**left); + } + else { + line_i -= left.line_count; + cur_node = &(**right); + } + } + } + } + + node_stack.push(cur_node); + + BufferNodeLineIter { + node_stack: node_stack, + } + } } @@ -764,6 +821,39 @@ impl<'a> Iterator<&'a str> for BufferNodeGraphemeIter<'a> { +/// An iterator over a text buffer's lines +pub struct BufferNodeLineIter<'a> { + node_stack: Vec<&'a BufferNode>, +} + + +impl<'a> Iterator<&'a Line> for BufferNodeLineIter<'a> { + fn next(&mut self) -> Option<&'a Line> { + loop { + if let Option::Some(node) = self.node_stack.pop() { + match node.data { + BufferNodeData::Leaf(ref line) => { + return Some(line); + }, + + BufferNodeData::Branch(ref left, ref right) => { + self.node_stack.push(&(**right)); + self.node_stack.push(&(**left)); + continue; + } + } + } + else { + return None; + } + } + } + + +} + + + //==================================================================== // TESTS //==================================================================== diff --git a/src/term_ui.rs b/src/term_ui.rs index 40de871..cef5428 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -32,14 +32,14 @@ pub struct TermUI { impl TermUI { pub fn new() -> TermUI { TermUI { - rb: rustbox::RustBox::init(&[None]).unwrap(), + rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(), editor: Editor::new(), } } pub fn new_from_editor(editor: Editor) -> TermUI { TermUI { - rb: rustbox::RustBox::init(&[None]).unwrap(), + rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(), editor: editor, } } @@ -152,94 +152,64 @@ impl TermUI { } pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) { - let mut tb_iter = editor.buffer.grapheme_iter_at_index(editor.buffer.pos_2d_to_closest_1d(editor.view_pos)); - let mut pline = c1.0; - let mut pcol = c1.1; - let mut line = editor.view_pos.0; - let mut column = editor.view_pos.1; - let mut pos = editor.buffer.pos_2d_to_closest_1d(editor.view_pos); - let max_line = line + (c2.0 - c1.0); - let max_col = column + (c2.1 - c1.1); + let mut line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0); - let cursor_pos = editor.buffer.pos_2d_to_closest_1d(editor.cursor); + let mut line_num = editor.view_pos.0; + let mut col_num = editor.view_pos.1; + + let mut print_line_num = c1.0; + let mut print_col_num = c1.1; + + let max_print_line = c2.0 - c1.0; + let max_print_col = c2.1 - c1.1; + + let cursor_pos_1d = editor.buffer.pos_2d_to_closest_1d(editor.cursor); + let cursor_pos = editor.buffer.pos_1d_to_closest_2d(cursor_pos_1d); + let print_cursor_pos = (cursor_pos.0 + editor.view_pos.0, cursor_pos.1 + editor.view_pos.1); loop { - if let Some(g) = tb_iter.next() { - if is_line_ending(g) { - if pos == cursor_pos { - self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - } - - pline += 1; - pcol = c1.1; - line += 1; - column = 0; - } - else { - if pos == cursor_pos { - self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, g); + if let Some(line) = line_iter.next() { + let mut g_iter = line.grapheme_iter(); + g_iter.skip_graphemes(editor.view_pos.1); + + for g in g_iter { + if is_line_ending(g) { + if (line_num, col_num) == cursor_pos { + self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + } } else { - self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::White, Color::Black, g); + if (line_num, col_num) == cursor_pos { + self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g); + } + else { + self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, g); + } } - pcol += 1; - column += 1; - } - } - else { - // Show cursor at end of document if it's past the end of - // the document - if cursor_pos >= pos { - self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, " "); - } - - return; - } - - if line > max_line { - return; - } - - // If we're past the edge of the display, go to the next line - if column > max_col { - tb_iter.next_line(); - - pline += 1; - pcol = c1.1; - line += 1; - column = 0; - - if line > max_line { - return; - } - } - - // If we're before the edge of the display, move forward to get - // to it. - loop { - if column < editor.view_pos.1 { - let nl = tb_iter.skip_non_newline_graphemes(editor.view_pos.1); - if !nl { - column = editor.view_pos.1; + col_num += 1; + print_col_num += 1; + + if print_col_num > max_print_col { break; } - else { - pline += 1; - line += 1; - } - - if line > max_line { - return; - } - } - else { - break; } } + else if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 < c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 < c2.1 { + if cursor_pos_1d >= editor.buffer.len() { + self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + } + break; + } - // Get the 1d position of the char to be printed next - pos = editor.buffer.pos_2d_to_closest_1d((line, column)); + line_num += 1; + print_line_num += 1; + col_num = editor.view_pos.1; + print_col_num = c1.1; + + if print_line_num > max_print_line { + break; + } } } diff --git a/todo.md b/todo.md index 849f16a..4a080b8 100644 --- a/todo.md +++ b/todo.md @@ -5,11 +5,3 @@ - Editor info display (filename, current line/column, indentation style, etc.) - File opening by entering path - UI that wraps editors, for split view. -- Unit testing for text block, text node, and text buffer. They must - be reliable! - -- Change text data structure to store lines explicitly. Still use a tree - structure to hold the lines, but just store the lines themselves as - straight vectors for now. Be mindful to keep the API's clean enough - that you can substitute another internal storage approach for lines - later on. \ No newline at end of file