From 833e92c5a059a53be52762beb3384c66ab45eeb6 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 26 Dec 2014 21:03:13 -0800 Subject: [PATCH] Added a some text manipulation methods, and made backspace work properly. --- src/buffer/mod.rs | 26 +++---- src/buffer/text_block.rs | 2 +- src/buffer/text_node.rs | 92 +++++++++++++++++++++++- src/editor.rs | 39 +++++----- src/main.rs | 1 + src/{buffer/utils.rs => string_utils.rs} | 0 6 files changed, 121 insertions(+), 39 deletions(-) rename src/{buffer/utils.rs => string_utils.rs} (100%) diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 85c8087..3d64823 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -4,7 +4,6 @@ use std::fmt; use std; use self::text_node::{TextNode, TextNodeData}; -mod utils; mod text_block; mod text_node; @@ -20,19 +19,22 @@ impl TextBuffer { root: TextNode::new() } } + pub fn len(&self) -> uint { self.root.char_count } + pub fn newline_count(&self) -> uint { self.root.newline_count } + - //pub fn pos_2d_to_1d(&self, pos: (uint, uint)) -> Option { - // // TODO - // return Option::None; - //} + pub fn end_of_line(&self, pos: uint) -> uint { + self.root.end_of_line(pos) + } + pub fn pos_2d_to_closest_1d(&self, pos: (uint, uint)) -> uint { match self.root.pos_2d_to_closest_1d(0, pos) { @@ -40,30 +42,24 @@ impl TextBuffer { _ => self.len() } } - - //pub fn pos_2d_to_closest_2d(&self, pos: (uint, uint)) -> (uint, uint) { - // // TODO - // return (0, 0); - //} - - //pub fn pos_1d_to_2d(&self, pos: uint) -> Option<(uint, uint)> { - // // TODO - // return Option::None; - //} + pub fn pos_1d_to_closest_2d(&self, pos: uint) -> (uint, uint) { self.root.pos_1d_to_closest_2d((0,0), pos) } + /// Insert 'text' at char position 'pos'. pub fn insert_text(&mut self, text: &str, pos: uint) { self.root.insert_text(text, pos); } + /// Remove the text between char positions 'pos_a' and 'pos_b'. pub fn remove_text(&mut self, pos_a: uint, pos_b: uint) { self.root.remove_text(pos_a, pos_b); } + pub fn root_iter<'a>(&'a self) -> TextBufferIter<'a> { let mut node_stack: Vec<&'a TextNode> = Vec::new(); diff --git a/src/buffer/text_block.rs b/src/buffer/text_block.rs index 7f2c087..899f91f 100644 --- a/src/buffer/text_block.rs +++ b/src/buffer/text_block.rs @@ -2,7 +2,7 @@ use std::mem; use std::fmt; -use super::utils::char_pos_to_byte_pos; +use string_utils::char_pos_to_byte_pos; /// A block of text, contiguous in memory pub struct TextBlock { diff --git a/src/buffer/text_node.rs b/src/buffer/text_node.rs index 7152646..f320a8d 100644 --- a/src/buffer/text_node.rs +++ b/src/buffer/text_node.rs @@ -4,7 +4,7 @@ use std::fmt; use std::mem; use std::cmp::{min, max}; -use super::utils::{newline_count, char_count, char_and_newline_count}; +use string_utils::{newline_count, char_count, char_and_newline_count}; use super::text_block::TextBlock; const MIN_LEAF_SIZE: uint = 64; @@ -461,6 +461,96 @@ impl TextNode { } + /// Returns the number of newlines contained within the + /// character range [pos_a, pos_b) + pub fn newlines_in_range(&self, pos_a: uint, pos_b: uint) -> uint { + if pos_a > pos_b { + panic!("newlines_in_range(): pos_a must be less than or equal to pos_b."); + } + + if pos_a == pos_b { + return 0; + } + + match self.data { + TextNodeData::Leaf(ref tb) => { + let mut iter = tb.as_str().chars(); + let mut count: uint = 0; + let mut i: uint = 0; + + for c in iter { + if i >= pos_b { + break; + } + + if i >= pos_a && c == '\n' { + count += 1; + } + + i += 1; + } + + return count; + }, + + TextNodeData::Branch(ref left, ref right) => { + let mut count: uint = 0; + + // Left + if pos_a == 0 && pos_b >= left.char_count { + count += left.newline_count; + } + else if pos_a < left.char_count { + count += left.newlines_in_range(pos_a, pos_b); + } + + // Right + if pos_a <= left.char_count && pos_b >= self.char_count { + count += right.newline_count; + } + else if pos_a < self.char_count && pos_b >= left.char_count { + let pa = if pos_a > left.char_count {pos_a - left.char_count} else {0}; + count += right.newlines_in_range(pa, pos_b - left.char_count); + } + + return count; + } + } + } + + + /// Starting at pos, find the end of the line and return its position + pub fn end_of_line(&self, pos: uint) -> uint { + match self.data { + TextNodeData::Leaf(ref tb) => { + let mut iter = tb.as_str().chars(); + let mut i: uint = 0; + + for c in iter { + if i >= pos { + if c == '\n' { + break; + } + } + + i += 1; + } + + return i; + }, + + TextNodeData::Branch(ref left, ref right) => { + if (left.char_count - left.tail_len()) > pos { + return left.end_of_line(pos); + } + else { + return right.end_of_line(pos - left.char_count); + } + } + } + } + + /// Returns the number of characters after the last newline in the node, /// or the total character length of the node if there are no newlines. pub fn tail_len(&self) -> uint { diff --git a/src/editor.rs b/src/editor.rs index 8bbd112..fc9ca68 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -3,6 +3,7 @@ use buffer::TextBuffer; use std::path::Path; use files::{load_file_to_buffer, save_buffer_to_file}; +use string_utils::char_count; pub struct Editor { @@ -58,19 +59,15 @@ impl Editor { 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); + let p = self.buffer.pos_2d_to_closest_1d(self.cursor); + // Insert text self.buffer.insert_text(text, pos); - self.dirty = true; - // TODO: handle multi-character strings properly - if text == "\n" { - self.cursor.0 += 1; - self.cursor.1 = 0; - } - else { - self.cursor.1 += 1; - } + // Move cursor + self.cursor = self.buffer.pos_1d_to_closest_2d(p + str_len); } pub fn insert_text_at_char(&mut self, text: &str, pos: uint) { @@ -83,31 +80,29 @@ impl Editor { let pos_b = self.buffer.pos_2d_to_closest_1d(self.cursor); let pos_a = if pos_b >= char_count {pos_b - char_count} else {0}; + // Move cursor + self.cursor = self.buffer.pos_1d_to_closest_2d(pos_a); + + // Remove text self.buffer.remove_text(pos_a, pos_b); self.dirty = true; - - // TODO: handle multi-character removal properly - if self.cursor.1 == 0 && self.cursor.0 > 0 { - self.cursor.0 -= 1; - } - else if self.cursor.1 > 0{ - self.cursor.1 -= 1; - } } pub fn cursor_left(&mut self) { let p = self.buffer.pos_2d_to_closest_1d(self.cursor); - self.cursor = self.buffer.pos_1d_to_closest_2d(p); - if self.cursor.1 > 0 { - self.cursor.1 -= 1; + + if p > 0 { + self.cursor = self.buffer.pos_1d_to_closest_2d(p - 1); + } + else { + self.cursor = self.buffer.pos_1d_to_closest_2d(0); } } pub fn cursor_right(&mut self) { - self.cursor.1 += 1; let p = self.buffer.pos_2d_to_closest_1d(self.cursor); - self.cursor = self.buffer.pos_1d_to_closest_2d(p); + self.cursor = self.buffer.pos_1d_to_closest_2d(p + 1); } pub fn cursor_up(&mut self) { diff --git a/src/main.rs b/src/main.rs index 86ccdaf..22a7b30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use docopt::Docopt; use editor::Editor; use term_ui::TermUI; +mod string_utils; mod buffer; mod files; mod editor; diff --git a/src/buffer/utils.rs b/src/string_utils.rs similarity index 100% rename from src/buffer/utils.rs rename to src/string_utils.rs