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:
parent
49b34e78d5
commit
cb5b79ec9c
|
@ -361,6 +361,14 @@ pub struct LineGraphemeIter<'a> {
|
||||||
done: bool,
|
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> {
|
impl<'a> Iterator<&'a str> for LineGraphemeIter<'a> {
|
||||||
fn next(&mut self) -> Option<&'a str> {
|
fn next(&mut self) -> Option<&'a str> {
|
||||||
if self.done {
|
if self.done {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
use self::node::{BufferNode, BufferNodeGraphemeIter};
|
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
|
||||||
use self::line::{Line};
|
use self::line::{Line};
|
||||||
use string_utils::{is_line_ending};
|
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
|
// 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]
|
#[test]
|
||||||
fn remove_lines_1() {
|
fn remove_lines_1() {
|
||||||
let mut buf = Buffer::new();
|
let mut buf = Buffer::new();
|
||||||
|
@ -966,4 +1021,3 @@ fn grapheme_iter_at_index_2() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -412,8 +412,6 @@ impl BufferNode {
|
||||||
total_side_removal = true;
|
total_side_removal = true;
|
||||||
mem::swap(&mut temp_node, &mut (**left));
|
mem::swap(&mut temp_node, &mut (**left));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// Partial removal of one or both sides
|
// Partial removal of one or both sides
|
||||||
else {
|
else {
|
||||||
|
@ -443,7 +441,7 @@ impl BufferNode {
|
||||||
}
|
}
|
||||||
line.remove_text(pos_a, pos_b2);
|
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
|
// TESTS
|
||||||
//====================================================================
|
//====================================================================
|
||||||
|
|
120
src/term_ui.rs
120
src/term_ui.rs
|
@ -32,14 +32,14 @@ pub struct TermUI {
|
||||||
impl TermUI {
|
impl TermUI {
|
||||||
pub fn new() -> TermUI {
|
pub fn new() -> TermUI {
|
||||||
TermUI {
|
TermUI {
|
||||||
rb: rustbox::RustBox::init(&[None]).unwrap(),
|
rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(),
|
||||||
editor: Editor::new(),
|
editor: Editor::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_editor(editor: Editor) -> TermUI {
|
pub fn new_from_editor(editor: Editor) -> TermUI {
|
||||||
TermUI {
|
TermUI {
|
||||||
rb: rustbox::RustBox::init(&[None]).unwrap(),
|
rb: rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap(),
|
||||||
editor: editor,
|
editor: editor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,94 +152,64 @@ impl TermUI {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) {
|
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 line_iter = editor.buffer.line_iter_at_index(editor.view_pos.0);
|
||||||
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 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 {
|
loop {
|
||||||
if let Some(g) = tb_iter.next() {
|
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 is_line_ending(g) {
|
||||||
if pos == cursor_pos {
|
if (line_num, col_num) == cursor_pos {
|
||||||
self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
|
self.rb.print(print_col_num, print_line_num, 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);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::White, Color::Black, g);
|
|
||||||
}
|
|
||||||
|
|
||||||
pcol += 1;
|
|
||||||
column += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Show cursor at end of document if it's past the end of
|
if (line_num, col_num) == cursor_pos {
|
||||||
// the document
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g);
|
||||||
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;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pline += 1;
|
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, g);
|
||||||
line += 1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if line > max_line {
|
col_num += 1;
|
||||||
return;
|
print_col_num += 1;
|
||||||
}
|
|
||||||
}
|
if print_col_num > max_print_col {
|
||||||
else {
|
|
||||||
break;
|
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
|
line_num += 1;
|
||||||
pos = editor.buffer.pos_2d_to_closest_1d((line, column));
|
print_line_num += 1;
|
||||||
|
col_num = editor.view_pos.1;
|
||||||
|
print_col_num = c1.1;
|
||||||
|
|
||||||
|
if print_line_num > max_print_line {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
8
todo.md
8
todo.md
|
@ -5,11 +5,3 @@
|
||||||
- Editor info display (filename, current line/column, indentation style, etc.)
|
- Editor info display (filename, current line/column, indentation style, etc.)
|
||||||
- File opening by entering path
|
- File opening by entering path
|
||||||
- UI that wraps editors, for split view.
|
- 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.
|
|
Loading…
Reference in New Issue
Block a user