diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs new file mode 100644 index 0000000..a5f479a --- /dev/null +++ b/src/buffer/mod.rs @@ -0,0 +1,143 @@ + + + + +use std::fmt; +use std; +use self::text_node::{TextNode, TextNodeData}; + +mod utils; +mod text_block; +mod text_node; + + +/// A text buffer +pub struct TextBuffer { + pub root: TextNode +} + +impl TextBuffer { + pub fn new() -> TextBuffer { + TextBuffer { + root: TextNode::new() + } + } + + pub fn len(&self) -> uint { + self.root.char_count + } + + /// 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(); + let mut cur_node = &self.root; + + loop { + match cur_node.data { + TextNodeData::Leaf(_) => { + break; + }, + + TextNodeData::Branch(ref left, ref right) => { + node_stack.push(&(**right)); + cur_node = &(**left); + } + } + } + + TextBufferIter { + node_stack: node_stack, + cur_block: match cur_node.data { + TextNodeData::Leaf(ref tb) => tb.as_str().chars(), + _ => panic!("This should never happen.") + } + } + } +} + +impl fmt::Show for TextBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.root.fmt(f) + } +} + + + + +/// An iterator over a text buffer +pub struct TextBufferIter<'a> { + node_stack: Vec<&'a TextNode>, + cur_block: std::str::Chars<'a>, +} + + +impl<'a> TextBufferIter<'a> { + // Puts the iterator on the next line + pub fn next_line(&mut self) -> Option { + // TODO: more efficient implementation, taking advantage of rope + // structure. + for c in *self { + if c == '\n' { + return Option::Some(c); + } + } + + return Option::None; + } + + + // Skips the iterator n characters ahead + pub fn skip_chars(&mut self, n: uint) { + // TODO: more efficient implementation, taking advantage of rope + // structure. + for _ in range(0, n) { + if let Option::None = self.next() { + break; + } + } + } +} + + +impl<'a> Iterator for TextBufferIter<'a> { + fn next(&mut self) -> Option { + if let Option::Some(c) = self.cur_block.next() { + return Option::Some(c); + } + + loop { + if let Option::Some(node) = self.node_stack.pop() { + match node.data { + TextNodeData::Leaf(ref tb) => { + self.cur_block = tb.as_str().chars(); + + if let Option::Some(c) = self.cur_block.next() { + return Option::Some(c); + } + else { + continue; + } + }, + + TextNodeData::Branch(ref left, ref right) => { + self.node_stack.push(&(**right)); + self.node_stack.push(&(**left)); + continue; + } + } + } + else { + return Option::None; + } + } + } +} \ No newline at end of file diff --git a/src/buffer/text_block.rs b/src/buffer/text_block.rs new file mode 100644 index 0000000..7f2c087 --- /dev/null +++ b/src/buffer/text_block.rs @@ -0,0 +1,102 @@ +#![allow(dead_code)] + +use std::mem; +use std::fmt; +use super::utils::char_pos_to_byte_pos; + +/// A block of text, contiguous in memory +pub struct TextBlock { + pub data: Vec, +} + +impl TextBlock { + /// Create a new empty text block. + pub fn new() -> TextBlock { + TextBlock { + data: Vec::::new() + } + } + + /// 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() + } + + /// Insert 'text' at char position 'pos'. + pub fn insert_text(&mut self, text: &str, pos: uint) { + // Find insertion position in bytes + let byte_pos = char_pos_to_byte_pos(self.as_str(), pos); + + // Grow data size + self.data.grow(text.len(), 0); + + // Move old bytes forward + let mut from = self.data.len() - text.len(); + let mut to = self.data.len(); + while from > byte_pos { + from -= 1; + to -= 1; + + self.data[to] = self.data[from]; + } + + // Copy new bytes in + let mut i = byte_pos; + for b in text.bytes() { + self.data[i] = b; + i += 1 + } + } + + /// Remove the text between char positions 'pos_a' and 'pos_b'. + pub fn remove_text(&mut self, pos_a: uint, pos_b: uint) { + // Bounds checks + if pos_a > pos_b { + panic!("TextBlock::remove_text(): pos_a must be less than or equal to pos_b."); + } + + // Find removal positions in bytes + let byte_pos_a = char_pos_to_byte_pos(self.as_str(), pos_a); + let byte_pos_b = char_pos_to_byte_pos(self.as_str(), pos_b); + + // Move bytes to fill in the gap left by the removed bytes + let mut from = byte_pos_b; + let mut to = byte_pos_a; + while from < self.data.len() { + self.data[to] = self.data[from]; + + from += 1; + to += 1; + } + + // Remove data from the end + let final_size = self.data.len() + byte_pos_a - byte_pos_b; + self.data.truncate(final_size); + } + + /// Returns an immutable string slice into the text block's memory + pub fn as_str<'a>(&'a self) -> &'a str { + unsafe { + mem::transmute(self.data.as_slice()) + } + } +} + +impl fmt::Show for TextBlock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} \ No newline at end of file diff --git a/src/buffer.rs b/src/buffer/text_node.rs similarity index 60% rename from src/buffer.rs rename to src/buffer/text_node.rs index 78053a1..fdfab2e 100644 --- a/src/buffer.rs +++ b/src/buffer/text_node.rs @@ -1,159 +1,14 @@ #![allow(dead_code)] -use std::cmp::{min, max}; -use std::mem; use std::fmt; -use std; - - -fn newline_count(text: &str) -> uint { - let mut count = 0; - for c in text.chars() { - if c == '\n' { - count += 1; - } - } - return count; -} - -fn char_count(text: &str) -> uint { - let mut count = 0; - for _ in text.chars() { - count += 1; - } - return count; -} - -fn char_and_newline_count(text: &str) -> (uint, uint) { - let mut char_count = 0; - let mut newline_count = 0; - - for c in text.chars() { - char_count += 1; - if c == '\n' { - newline_count += 1; - } - } - - return (char_count, newline_count); -} - -fn char_pos_to_byte_pos(text: &str, pos: uint) -> uint { - let mut i: uint = 0; - - for (offset, _) in text.char_indices() { - if i == pos { - return offset; - } - i += 1; - } - - if i == pos { - return text.len(); - } - - panic!("char_pos_to_byte_pos(): char position off the end of the string."); -} - - -/// A block of text, contiguous in memory -pub struct TextBlock { - pub data: Vec, -} - -impl TextBlock { - /// Create a new empty text block. - pub fn new() -> TextBlock { - TextBlock { - data: Vec::::new() - } - } - - /// 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() - } - - /// Insert 'text' at char position 'pos'. - pub fn insert_text(&mut self, text: &str, pos: uint) { - // Find insertion position in bytes - let byte_pos = char_pos_to_byte_pos(self.as_str(), pos); - - // Grow data size - self.data.grow(text.len(), 0); - - // Move old bytes forward - let mut from = self.data.len() - text.len(); - let mut to = self.data.len(); - while from > byte_pos { - from -= 1; - to -= 1; - - self.data[to] = self.data[from]; - } - - // Copy new bytes in - let mut i = byte_pos; - for b in text.bytes() { - self.data[i] = b; - i += 1 - } - } - - /// Remove the text between char positions 'pos_a' and 'pos_b'. - pub fn remove_text(&mut self, pos_a: uint, pos_b: uint) { - // Bounds checks - if pos_a > pos_b { - panic!("TextBlock::remove_text(): pos_a must be less than or equal to pos_b."); - } - - // Find removal positions in bytes - let byte_pos_a = char_pos_to_byte_pos(self.as_str(), pos_a); - let byte_pos_b = char_pos_to_byte_pos(self.as_str(), pos_b); - - // Move bytes to fill in the gap left by the removed bytes - let mut from = byte_pos_b; - let mut to = byte_pos_a; - while from < self.data.len() { - self.data[to] = self.data[from]; - - from += 1; - to += 1; - } - - // Remove data from the end - let final_size = self.data.len() + byte_pos_a - byte_pos_b; - self.data.truncate(final_size); - } - - /// Returns an immutable string slice into the text block's memory - pub fn as_str<'a>(&'a self) -> &'a str { - unsafe { - mem::transmute(self.data.as_slice()) - } - } -} - -impl fmt::Show for TextBlock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_str()) - } -} +use std::mem; +use std::cmp::{min, max}; +use super::utils::{newline_count, char_count, char_and_newline_count}; +use super::text_block::TextBlock; +const MIN_LEAF_SIZE: uint = 64; +const MAX_LEAF_SIZE: uint = MIN_LEAF_SIZE * 2; /// A text rope node, using TextBlocks for its underlying text @@ -171,9 +26,6 @@ pub enum TextNodeData { Branch(Box, Box) } -const MIN_LEAF_SIZE: uint = 64; -const MAX_LEAF_SIZE: uint = MIN_LEAF_SIZE * 2; - impl TextNode { pub fn new() -> TextNode { @@ -487,138 +339,4 @@ impl fmt::Show for TextNode { } } } -} - - - - -/// A text buffer -pub struct TextBuffer { - pub root: TextNode -} - -impl TextBuffer { - pub fn new() -> TextBuffer { - TextBuffer { - root: TextNode::new() - } - } - - pub fn len(&self) -> uint { - self.root.char_count - } - - /// 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(); - let mut cur_node = &self.root; - - loop { - match cur_node.data { - TextNodeData::Leaf(_) => { - break; - }, - - TextNodeData::Branch(ref left, ref right) => { - node_stack.push(&(**right)); - cur_node = &(**left); - } - } - } - - TextBufferIter { - node_stack: node_stack, - cur_block: match cur_node.data { - TextNodeData::Leaf(ref tb) => tb.as_str().chars(), - _ => panic!("This should never happen.") - } - } - } -} - -impl fmt::Show for TextBuffer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.root.fmt(f) - } -} - - - - -/// An iterator over a text buffer -pub struct TextBufferIter<'a> { - node_stack: Vec<&'a TextNode>, - cur_block: std::str::Chars<'a>, -} - - -impl<'a> TextBufferIter<'a> { - // Puts the iterator on the next line - pub fn next_line(&mut self) -> Option { - // TODO: more efficient implementation, taking advantage of rope - // structure. - for c in *self { - if c == '\n' { - return Option::Some(c); - } - } - - return Option::None; - } - - - // Skips the iterator n characters ahead - pub fn skip_chars(&mut self, n: uint) { - // TODO: more efficient implementation, taking advantage of rope - // structure. - for _ in range(0, n) { - if let Option::None = self.next() { - break; - } - } - } -} - - -impl<'a> Iterator for TextBufferIter<'a> { - fn next(&mut self) -> Option { - if let Option::Some(c) = self.cur_block.next() { - return Option::Some(c); - } - - loop { - if let Option::Some(node) = self.node_stack.pop() { - match node.data { - TextNodeData::Leaf(ref tb) => { - self.cur_block = tb.as_str().chars(); - - if let Option::Some(c) = self.cur_block.next() { - return Option::Some(c); - } - else { - continue; - } - }, - - TextNodeData::Branch(ref left, ref right) => { - self.node_stack.push(&(**right)); - self.node_stack.push(&(**left)); - continue; - } - } - } - else { - return Option::None; - } - } - } } \ No newline at end of file diff --git a/src/buffer/utils.rs b/src/buffer/utils.rs new file mode 100644 index 0000000..f7c3d9e --- /dev/null +++ b/src/buffer/utils.rs @@ -0,0 +1,51 @@ +#![allow(dead_code)] +//! Misc helpful utility functions for TextBuffer related stuff. + +pub fn newline_count(text: &str) -> uint { + let mut count = 0; + for c in text.chars() { + if c == '\n' { + count += 1; + } + } + return count; +} + +pub fn char_count(text: &str) -> uint { + let mut count = 0; + for _ in text.chars() { + count += 1; + } + return count; +} + +pub fn char_and_newline_count(text: &str) -> (uint, uint) { + let mut char_count = 0; + let mut newline_count = 0; + + for c in text.chars() { + char_count += 1; + if c == '\n' { + newline_count += 1; + } + } + + return (char_count, newline_count); +} + +pub fn char_pos_to_byte_pos(text: &str, pos: uint) -> uint { + let mut i: uint = 0; + + for (offset, _) in text.char_indices() { + if i == pos { + return offset; + } + i += 1; + } + + if i == pos { + return text.len(); + } + + panic!("char_pos_to_byte_pos(): char position off the end of the string."); +} \ No newline at end of file diff --git a/src/files.rs b/src/files.rs index 8889a33..f2e096c 100644 --- a/src/files.rs +++ b/src/files.rs @@ -27,7 +27,7 @@ pub fn save_buffer_to_file(tb: &TextBuffer, path: &Path) -> IoResult<()> { let mut f = BufferedWriter::new(try!(File::create(path))); for c in iter { - f.write_char(c); + let _ = f.write_char(c); } return Ok(()); diff --git a/src/main.rs b/src/main.rs index f2c5e66..9b0ec63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ +#![allow(dead_code)] + extern crate rustbox; extern crate docopt; extern crate serialize; - use std::char; use std::path::Path; use docopt::Docopt; @@ -36,7 +37,7 @@ Options: const K_ENTER: u16 = 13; const K_TAB: u16 = 9; const K_SPACE: u16 = 32; -const K_BACKSPACE: u16 = 127; +//const K_BACKSPACE: u16 = 127; //const K_DOWN: u16 = 65516; //const K_LEFT: u16 = 65515; //const K_RIGHT: u16 = 65514; @@ -119,7 +120,7 @@ fn main() { }, K_CTRL_S => { - save_buffer_to_file(&tb, &Path::new("untitled.txt")); + let _ = save_buffer_to_file(&tb, &Path::new("untitled.txt")); }, K_ENTER => {