diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 3d64823..5cd1fcf 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -61,6 +61,7 @@ impl TextBuffer { } + /// Creates an iterator at the first character pub fn root_iter<'a>(&'a self) -> TextBufferIter<'a> { let mut node_stack: Vec<&'a TextNode> = Vec::new(); let mut cur_node = &self.root; @@ -86,6 +87,49 @@ impl TextBuffer { } } } + + + /// Creates an iterator starting at the specified character index. + /// If the index is past the end of the text, then the iterator will + /// return None on next(). + pub fn iter_at_char<'a>(&'a self, index: uint) -> TextBufferIter<'a> { + let mut node_stack: Vec<&'a TextNode> = Vec::new(); + let mut cur_node = &self.root; + let mut char_i = index; + + loop { + match cur_node.data { + TextNodeData::Leaf(_) => { + let mut char_iter = match cur_node.data { + TextNodeData::Leaf(ref tb) => tb.as_str().chars(), + _ => panic!("This should never happen.") + }; + + while char_i > 0 { + char_iter.next(); + char_i -= 1; + } + + return TextBufferIter { + node_stack: node_stack, + cur_block: char_iter, + }; + }, + + TextNodeData::Branch(ref left, ref right) => { + if left.char_count > char_i { + node_stack.push(&(**right)); + cur_node = &(**left); + } + else { + cur_node = &(**right); + char_i -= left.char_count; + } + } + } + } + } + } impl fmt::Show for TextBuffer { @@ -129,6 +173,30 @@ impl<'a> TextBufferIter<'a> { } } } + + + // Skips the iterator n characters ahead, unless it hits a newline + // character. If it hits a newline character, returns true, otherwise, + // false. + pub fn skip_non_newline_chars(&mut self, n: uint) -> bool { + // TODO: more efficient implementation, taking advantage of rope + // structure. + for _ in range(0, n) { + match self.next() { + Option::Some(c) => { + if c == '\n' { + return true; + } + }, + + Option::None => { + break; + } + } + } + + return false; + } } diff --git a/src/editor.rs b/src/editor.rs index fc9ca68..b135ff1 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -57,6 +57,26 @@ impl Editor { self.view_dim = (h, w); } + + /// Moves the editor's view the minimum amount to show the cursor + pub fn move_view_to_cursor(&mut self) { + // Horizontal + if self.cursor.1 < self.view_pos.1 { + self.view_pos.1 = self.cursor.1; + } + else if self.cursor.1 >= (self.view_pos.1 + self.view_dim.1) { + self.view_pos.1 = 1 + self.cursor.1 - self.view_dim.1; + } + + // Vertical + if self.cursor.0 < self.view_pos.0 { + self.view_pos.0 = self.cursor.0; + } + else if self.cursor.0 >= (self.view_pos.0 + self.view_dim.0) { + self.view_pos.0 = 1 + self.cursor.0 - self.view_dim.0; + } + } + pub fn insert_text_at_cursor(&mut self, text: &str) { let pos = self.buffer.pos_2d_to_closest_1d(self.cursor); let str_len = char_count(text); @@ -68,6 +88,8 @@ impl Editor { // Move cursor self.cursor = self.buffer.pos_1d_to_closest_2d(p + str_len); + + self.move_view_to_cursor(); } pub fn insert_text_at_char(&mut self, text: &str, pos: uint) { @@ -87,6 +109,8 @@ impl Editor { self.buffer.remove_text(pos_a, pos_b); self.dirty = true; + + self.move_view_to_cursor(); } pub fn cursor_left(&mut self) { @@ -98,22 +122,30 @@ impl Editor { else { self.cursor = self.buffer.pos_1d_to_closest_2d(0); } + + self.move_view_to_cursor(); } pub fn cursor_right(&mut self) { let p = self.buffer.pos_2d_to_closest_1d(self.cursor); self.cursor = self.buffer.pos_1d_to_closest_2d(p + 1); + + self.move_view_to_cursor(); } pub fn cursor_up(&mut self) { if self.cursor.0 > 0 { self.cursor.0 -= 1; } + + self.move_view_to_cursor(); } pub fn cursor_down(&mut self) { if self.cursor.0 < self.buffer.newline_count() { self.cursor.0 += 1; } + + self.move_view_to_cursor(); } } diff --git a/src/term_ui.rs b/src/term_ui.rs index 2e435c9..fb3b9d0 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -48,6 +48,7 @@ impl TermUI { let mut width = self.rb.width(); let mut height = self.rb.height(); + self.editor.update_dim(height, width); loop { // Draw the editor to screen @@ -121,6 +122,7 @@ impl TermUI { Ok(rustbox::Event::ResizeEvent(w, h)) => { width = w as uint; height = h as uint; + self.editor.update_dim(height, width); } _ => { @@ -140,12 +142,14 @@ impl TermUI { } pub fn draw_editor(&self, editor: &Editor, c1: (uint, uint), c2: (uint, uint)) { - let mut tb_iter = editor.buffer.root_iter(); - let mut line: uint = 0; - let mut column: uint = 0; - let mut pos: uint = 0; - let height = c2.0 - c1.0; - let width = c2.1 - c1.1; + let mut tb_iter = editor.buffer.iter_at_char(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 cursor_pos = editor.buffer.pos_2d_to_closest_1d(editor.cursor); @@ -153,20 +157,23 @@ impl TermUI { if let Option::Some(c) = tb_iter.next() { if c == '\n' { if pos == cursor_pos { - self.rb.print(column, line, rustbox::RB_NORMAL, Color::Black, Color::White, " ".to_string().as_slice()); + self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, " ".to_string().as_slice()); } + pline += 1; + pcol = c1.1; line += 1; column = 0; } else { if pos == cursor_pos { - self.rb.print(column, line, rustbox::RB_NORMAL, Color::Black, Color::White, c.to_string().as_slice()); + self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, c.to_string().as_slice()); } else { - self.rb.print(column, line, rustbox::RB_NORMAL, Color::White, Color::Black, c.to_string().as_slice()); + self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::White, Color::Black, c.to_string().as_slice()); } + pcol += 1; column += 1; } } @@ -174,24 +181,57 @@ impl TermUI { // Show cursor at end of document if it's past the end of // the document if cursor_pos >= pos { - self.rb.print(column, line, rustbox::RB_NORMAL, Color::Black, Color::White, " "); + self.rb.print(pcol, pline, rustbox::RB_NORMAL, Color::Black, Color::White, " "); } - break; + return; } - pos += 1; - - if line > height { - break; + if line > max_line { + return; } - if column > width { + // 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; - pos = editor.buffer.pos_2d_to_closest_1d((line, column)); - } + + 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_chars(editor.view_pos.1); + if !nl { + column = editor.view_pos.1; + break; + } + else { + pline += 1; + line += 1; + } + + if line > max_line { + return; + } + } + else { + break; + } + } + + // Get the 1d position of the char to be printed next + pos = editor.buffer.pos_2d_to_closest_1d((line, column)); } } + + } \ No newline at end of file