Split buffer.rs into various sub-modules for better organization.
This commit is contained in:
parent
baafe5284d
commit
421b5288a4
143
src/buffer/mod.rs
Normal file
143
src/buffer/mod.rs
Normal file
|
@ -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<char> {
|
||||
// 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<char> for TextBufferIter<'a> {
|
||||
fn next(&mut self) -> Option<char> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
102
src/buffer/text_block.rs
Normal file
102
src/buffer/text_block.rs
Normal file
|
@ -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<u8>,
|
||||
}
|
||||
|
||||
impl TextBlock {
|
||||
/// Create a new empty text block.
|
||||
pub fn new() -> TextBlock {
|
||||
TextBlock {
|
||||
data: Vec::<u8>::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::<u8>::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())
|
||||
}
|
||||
}
|
|
@ -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<u8>,
|
||||
}
|
||||
|
||||
impl TextBlock {
|
||||
/// Create a new empty text block.
|
||||
pub fn new() -> TextBlock {
|
||||
TextBlock {
|
||||
data: Vec::<u8>::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::<u8>::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<TextNode>, Box<TextNode>)
|
||||
}
|
||||
|
||||
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<char> {
|
||||
// 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<char> for TextBufferIter<'a> {
|
||||
fn next(&mut self) -> Option<char> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
src/buffer/utils.rs
Normal file
51
src/buffer/utils.rs
Normal file
|
@ -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.");
|
||||
}
|
|
@ -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(());
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in New Issue
Block a user