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.
This commit is contained in:
Nathan Vegdahl 2014-12-31 20:19:12 -08:00
parent 49b34e78d5
commit cb5b79ec9c
5 changed files with 205 additions and 91 deletions

View File

@ -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 {

View File

@ -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};
@ -140,6 +140,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() {

View File

@ -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;
},
}
@ -681,6 +679,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
//====================================================================

View File

@ -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, " ");
}
if let Some(line) = line_iter.next() {
let mut g_iter = line.grapheme_iter();
g_iter.skip_graphemes(editor.view_pos.1);
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);
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, " ");
}
col_num += 1;
print_col_num += 1;
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;
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;
}
}
}

View File

@ -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.