diff --git a/src/buffer.rs b/src/buffer.rs index 6b3af29..72a3137 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,17 @@ use std::mem; +use std::fmt; + + +fn newline_count(text: &str) -> uint { + let mut count = 0; + for c in text.chars() { + if c == '\n' { + count += 1; + } + } + return count; +} + /// A block of text, contiguous in memory pub struct TextBlock { @@ -13,6 +26,19 @@ impl TextBlock { } } + /// Create a new text block with the contents of 'text'. + pub fn new_from_str(text: &str) -> TextBlock { + let mut tb = TextBlock { + data: Vec::::with_capacity(text.len()) + }; + + for b in text.bytes() { + tb.data.push(b); + } + + return tb; + } + /// Return the length of the text block in bytes. pub fn len(&self) -> uint { self.data.len() @@ -85,16 +111,185 @@ impl TextBlock { } } +impl fmt::Show for TextBlock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} -// /// A rope text storage buffer, using TextBlocks for its underlying text -// /// storage. -// pub enum TextBuffer { -// Block(TextBlock), -// Node(Box, Box) -// } -// -// impl TextBuffer { -// pub fn new() -> TextBuffer { -// TextBuffer::Block(TextBlock::new()) -// } -// } \ No newline at end of file + + + +/// A text rope node, using TextBlocks for its underlying text +/// storage. +// TODO: record number of graphines as well, to support utf8 properly +pub struct TextNode { + pub data: TextNodeData, + pub newline_count: uint, + pub byte_count: uint, +} + +pub enum TextNodeData { + Leaf(TextBlock), + Branch(Box, Box) +} + +const MIN_LEAF_SIZE: uint = 512; +const MAX_LEAF_SIZE: uint = MIN_LEAF_SIZE * 2; + + +impl TextNode { + pub fn new() -> TextNode { + TextNode { + data: TextNodeData::Leaf(TextBlock::new()), + newline_count: 0, + byte_count: 0 + } + } + + pub fn new_from_str(text: &str) -> TextNode { + TextNode { + data: TextNodeData::Leaf(TextBlock::new_from_str(text)), + newline_count: newline_count(text), + byte_count: text.len() + } + } + + /// Splits a leaf node into equal-sized pieces until all children are + /// less than 'max_size'. + pub fn split(&mut self) { + if let TextNodeData::Branch(_, _) = self.data { + panic!("TextNode::split(): attempt to split a non-leaf node."); + } + + if self.byte_count > 1 { + // Split data into two new text blocks + let mut tn1 = box TextNode::new(); + let mut tn2 = box TextNode::new(); + if let TextNodeData::Leaf(ref mut tb) = self.data { + let pos = tb.len() / 2; + tn1 = box TextNode::new_from_str(tb.as_str().slice(0, pos)); + tn2 = box TextNode::new_from_str(tb.as_str().slice(pos, tb.len())); + } + + // Swap the old and new data + let mut new_data = TextNodeData::Branch(tn1, tn2); + mem::swap(&mut self.data, &mut new_data); + } + } + + /// Merges the data of a non-leaf node to make it a leaf node + pub fn merge(&mut self) { + if let TextNodeData::Branch(_, _) = self.data { + let mut s: String = String::from_str(""); + + if let TextNodeData::Branch(ref mut left, ref mut right) = self.data { + // Merge left and right children first, to make sure we're dealing + // with leafs + if let TextNodeData::Branch(_, _) = left.data { left.merge(); } + if let TextNodeData::Branch(_, _) = right.data { right.merge(); } + + // Push data into a string + if let TextNodeData::Leaf(ref tb) = left.data { + s.push_str(tb.as_str()); + } + if let TextNodeData::Leaf(ref tb) = right.data { + s.push_str(tb.as_str()); + } + } + + self.data = TextNodeData::Leaf(TextBlock::new_from_str(s.as_slice())); + } + } + + /// Insert 'text' at position 'pos'. + pub fn insert_text(&mut self, text: &str, pos: uint) { + if pos > self.byte_count { + panic!("TextNode::insert_text(): attempt to insert text after end of node text."); + } + + match self.data { + TextNodeData::Leaf(_) => { + if let TextNodeData::Leaf(ref mut tb) = self.data { + tb.insert_text(text, pos); + } + self.newline_count += newline_count(text); + self.byte_count += text.len(); + + if self.byte_count > MAX_LEAF_SIZE { + self.split(); + } + }, + + TextNodeData::Branch(ref mut left, ref mut right) => { + if pos <= left.byte_count { + left.insert_text(text, pos); + } + else { + right.insert_text(text, pos - left.byte_count); + } + + self.newline_count = left.newline_count + right.newline_count; + self.byte_count = left.byte_count + right.byte_count; + } + } + } +} + +impl fmt::Show for TextNode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.data { + TextNodeData::Leaf(ref tb) => { + tb.fmt(f) + }, + + TextNodeData::Branch(ref left, ref right) => { + try!(left.fmt(f)); + right.fmt(f) + } + } + } +} + + + + +/// A text buffer +pub struct TextBuffer { + root: TextNode +} + +impl TextBuffer { + pub fn new() -> TextBuffer { + TextBuffer { + root: TextNode::new() + } + } + + pub fn len(&self) -> uint { + self.root.byte_count + } + + /// Insert 'text' at byte position 'pos'. + pub fn insert_text(&mut self, text: &str, pos: uint) { + self.root.insert_text(text, pos); + } + + /// Remove the text between byte positions 'pos_a' and 'pos_b'. + pub fn remove_text(&mut self, pos_a: uint, pos_b: uint) { + match self.root.data { + TextNodeData::Leaf(ref mut tb) => { + tb.remove_text(pos_a, pos_b); + self.root.byte_count = tb.len(); + }, + + TextNodeData::Branch(_, _) => {} + } + } +} + +impl fmt::Show for TextBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.root.fmt(f) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5796dce..52275ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ extern crate docopt; extern crate serialize; use docopt::Docopt; -use buffer::TextBlock; +use buffer::TextBuffer; mod buffer; @@ -30,16 +30,14 @@ fn main() { // Get command-line arguments let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); - let mut tb = TextBlock::new(); + let mut tb = TextBuffer::new(); - for _ in range(0i, 1000) { - tb.insert_text("Hello", 0); - tb.insert_text("Goodbye", 0); + for _ in range(0i, 100) { tb.insert_text(args.arg_file.as_slice(), 0); - if tb.len() > 1024 { - tb.remove_text(0, 12 + args.arg_file.len()); - } + //if tb.len() > 1024 { + // tb.remove_text(0, args.arg_file.len()); + //} } - println!("{}", tb.as_str()); + println!("{}", tb); } \ No newline at end of file