Get appending at the end of the rope to work
This commit is contained in:
parent
02af9e4ea7
commit
77bc77cd20
|
@ -2,7 +2,7 @@ use std::{mem, ops::Deref};
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 10;
|
const CHUNK_SIZE: usize = 10;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct NodeId(usize);
|
struct NodeId(usize);
|
||||||
|
|
||||||
impl Deref for NodeId {
|
impl Deref for NodeId {
|
||||||
|
@ -12,14 +12,15 @@ impl Deref for NodeId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum Node {
|
enum Node {
|
||||||
Interior {
|
Interior {
|
||||||
// the number of characters to the left of this node
|
// the number of characters to the left of this node
|
||||||
// the number of lines to the left of this node
|
// the number of lines to the left of this node
|
||||||
char_count: usize,
|
char_count: usize,
|
||||||
|
|
||||||
left: NodeId,
|
left: Option<NodeId>,
|
||||||
right: NodeId,
|
right: Option<NodeId>,
|
||||||
},
|
},
|
||||||
Leaf(LeafNode),
|
Leaf(LeafNode),
|
||||||
}
|
}
|
||||||
|
@ -33,6 +34,7 @@ impl Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct LeafNode(String);
|
struct LeafNode(String);
|
||||||
|
|
||||||
impl LeafNode {
|
impl LeafNode {
|
||||||
|
@ -56,97 +58,55 @@ impl From<String> for LeafNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a Rope is a regular tree which adds on some extra behavior for dealing with a continuous data
|
/// A Rope is a regular tree which adds on some extra behavior for dealing with a continuous data
|
||||||
// structure.
|
/// structure. In this case, the nodes all contain strings, and the rope is arranged such that a
|
||||||
|
/// depth-first traversal will yield the entire contents of the rope in proper order.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Rope {
|
pub struct Rope {
|
||||||
node_count: usize,
|
node_count: usize,
|
||||||
contents: Vec<Option<Node>>,
|
contents: Vec<Option<Node>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rope {
|
impl Rope {
|
||||||
|
/// Insert text at an index within the document. loc is the number of characters from the
|
||||||
|
/// beginning.
|
||||||
pub fn insert_at(&mut self, loc: usize, text: String) {
|
pub fn insert_at(&mut self, loc: usize, text: String) {
|
||||||
unimplemented!();
|
match self.find_insertion_node_id(loc) {
|
||||||
}
|
None => {
|
||||||
|
|
||||||
pub fn append(&mut self, text: String) {
|
|
||||||
if self.node_count == 0 {
|
|
||||||
let node = Node::Leaf(LeafNode::from(text));
|
let node = Node::Leaf(LeafNode::from(text));
|
||||||
self.node_count += 1;
|
self.node_count += 1;
|
||||||
self.contents.push(Some(node));
|
self.contents.push(Some(node));
|
||||||
} else {
|
|
||||||
// Let's grab a node. If it's a leaf node, we need to convert it into a interior node.
|
|
||||||
// In doing so, we need to move the current leaf node into a left-hand child and create
|
|
||||||
// the right-hand child.
|
|
||||||
//
|
|
||||||
// If it's an interior node, we'll dig deeper. Not doing any tree rebalancing at the
|
|
||||||
// moment.
|
|
||||||
//
|
|
||||||
// First thing we need to know is what mode of node we have. That's going to determine
|
|
||||||
// which branch of operations we go into. We want to do this without borrowing the data
|
|
||||||
// for anything more than the match.
|
|
||||||
// let mut node = mem::replace(&mut self.contents[0], None);
|
|
||||||
match self.contents[0] {
|
|
||||||
Some(Node::Interior { ref left, ref right, .. }) => {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
Some(Node::Leaf(_)) => {
|
Some(id) => {
|
||||||
let Some(Node::Leaf(mut ln)) = mem::replace(&mut self.contents[0], None) else {
|
self.insert_at_node(id, text);
|
||||||
panic!("Should never leave an empty space in the node list")
|
|
||||||
};
|
|
||||||
let contents = ln.take();
|
|
||||||
|
|
||||||
let lnode = Node::Leaf(LeafNode::from(contents));
|
|
||||||
let rnode = Node::Leaf(LeafNode::from(text));
|
|
||||||
|
|
||||||
let lnode_id = self.node_count;
|
|
||||||
let rnode_id = self.node_count + 1;
|
|
||||||
|
|
||||||
let interior_node = Node::Interior {
|
|
||||||
char_count: lnode.len(),
|
|
||||||
left: NodeId(lnode_id),
|
|
||||||
right: NodeId(rnode_id),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = mem::replace(&mut self.contents[0], Some(interior_node));
|
|
||||||
|
|
||||||
self.node_count += 2;
|
|
||||||
self.contents.push(Some(lnode));
|
|
||||||
self.contents.push(Some(rnode));
|
|
||||||
}
|
}
|
||||||
None => panic!("Should never leave an empty space in the node list"),
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
let mut node = &mut self.contents[0];
|
|
||||||
if let Node::Leaf(s) = node {
|
|
||||||
let contents = s.take();
|
|
||||||
|
|
||||||
let lnode = Node::Leaf(LeafNode::from(contents));
|
|
||||||
let rnode = Node::Leaf(LeafNode::from(text));
|
|
||||||
self.contents.push(lnode);
|
|
||||||
self.contents.push(rnode);
|
|
||||||
let lnode_id = self.node_count;
|
|
||||||
let rnode_id = self.node_count + 1;
|
|
||||||
self.node_count += 2;
|
|
||||||
*node = Node::Interior{
|
|
||||||
char_count: 0,
|
|
||||||
left: NodeId(lnode_id),
|
|
||||||
right: NodeId(rnode_id),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Append text to the end of the document.
|
||||||
|
pub fn append(&mut self, text: String) {
|
||||||
|
self.insert_at(self.len(), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the entire rope back to a continuous String.
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
|
if self.contents.is_empty() {
|
||||||
|
return "".to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
let mut r = String::new();
|
let mut r = String::new();
|
||||||
let mut stack = vec![NodeId(0)];
|
let mut stack = vec![NodeId(0)];
|
||||||
|
|
||||||
while let Some(current_id) = stack.pop() {
|
while let Some(current_id) = stack.pop() {
|
||||||
let node = &self.contents[*current_id];
|
let node = &self.contents[*current_id];
|
||||||
match node {
|
match node {
|
||||||
Some(Node::Interior{ left, right, .. }) => {
|
Some(Node::Interior { left, right, .. }) => {
|
||||||
stack.push(*right);
|
if let Some(right_id) = *right {
|
||||||
stack.push(*left);
|
stack.push(right_id);
|
||||||
|
}
|
||||||
|
if let Some(left_id) = *left {
|
||||||
|
stack.push(left_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(Node::Leaf(ln)) => r.push_str(ln.as_str()),
|
Some(Node::Leaf(ln)) => r.push_str(ln.as_str()),
|
||||||
None => panic!("Should never leave an empty space in the node list"),
|
None => panic!("Should never leave an empty space in the node list"),
|
||||||
|
@ -156,6 +116,7 @@ impl Rope {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the length of the stored string.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
// This can be optimized later. Do a traversal of each right node. We already have
|
// This can be optimized later. Do a traversal of each right node. We already have
|
||||||
// character counts of each left tree. Only count the length of the final right leaf.
|
// character counts of each left tree. Only count the length of the final right leaf.
|
||||||
|
@ -177,6 +138,74 @@ impl Rope {
|
||||||
fn node_count(&self) -> usize {
|
fn node_count(&self) -> usize {
|
||||||
self.node_count
|
self.node_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find the node ID of the insertion point. This is not fully implemented, in that this
|
||||||
|
// function ignores the offset from the beginning. Because of that, it is also always inserting
|
||||||
|
// onto the right side, and never traversing down the left.
|
||||||
|
fn find_insertion_node_id(&self, _loc: usize) -> Option<NodeId> {
|
||||||
|
let mut current_id = NodeId(0);
|
||||||
|
loop {
|
||||||
|
match self.contents.get(*current_id) {
|
||||||
|
Some(Some(Node::Interior { ref right, .. })) => match right {
|
||||||
|
Some(id) => current_id = *id,
|
||||||
|
None => return Some(current_id),
|
||||||
|
},
|
||||||
|
Some(Some(Node::Leaf(_))) => return Some(current_id),
|
||||||
|
Some(None) => panic!("There should never be an empty node in the tree"),
|
||||||
|
// This only happens when the list is empty. Otherwise, we're detecting the None in
|
||||||
|
// advance.
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert text at a particular node location.
|
||||||
|
//
|
||||||
|
// This is not a self-balancing operation (yet). Once we know where text needs to be inserted,
|
||||||
|
// based on the offset from the beginning, we can grab that node and either replace it (if it
|
||||||
|
// is a Leaf node) or update it (if it is an Interior node).
|
||||||
|
//
|
||||||
|
// This function is currently naive, in that it will always assume that text needs to be added
|
||||||
|
// to the right side, which may not be correct.
|
||||||
|
fn insert_at_node(&mut self, id: NodeId, text: String) {
|
||||||
|
match self.contents[*id] {
|
||||||
|
Some(Node::Interior { ref mut right, .. }) => {
|
||||||
|
let new_node = Node::Leaf(LeafNode::from(text));
|
||||||
|
let new_node_id = NodeId(self.node_count + 1);
|
||||||
|
|
||||||
|
*right = Some(new_node_id);
|
||||||
|
self.contents.push(Some(new_node));
|
||||||
|
|
||||||
|
self.node_count += 1;
|
||||||
|
}
|
||||||
|
Some(Node::Leaf(_)) => {
|
||||||
|
let Some(Node::Leaf(mut ln)) = mem::replace(&mut self.contents[*id], None) else {
|
||||||
|
panic!("Should never leave an empty space in the node list")
|
||||||
|
};
|
||||||
|
let contents = ln.take();
|
||||||
|
|
||||||
|
let lnode = Node::Leaf(LeafNode::from(contents));
|
||||||
|
let rnode = Node::Leaf(LeafNode::from(text));
|
||||||
|
|
||||||
|
let lnode_id = self.node_count;
|
||||||
|
let rnode_id = self.node_count + 1;
|
||||||
|
|
||||||
|
let interior_node = Node::Interior {
|
||||||
|
char_count: lnode.len(),
|
||||||
|
left: Some(NodeId(lnode_id)),
|
||||||
|
right: Some(NodeId(rnode_id)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = mem::replace(&mut self.contents[*id], Some(interior_node));
|
||||||
|
|
||||||
|
self.node_count += 2;
|
||||||
|
self.contents.push(Some(lnode));
|
||||||
|
self.contents.push(Some(rnode));
|
||||||
|
|
||||||
|
}
|
||||||
|
None => panic!("Should never leave an empty space in the node list"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Rope {
|
impl Default for Rope {
|
||||||
|
@ -199,9 +228,9 @@ impl From<String> for Rope {
|
||||||
|
|
||||||
while lst.len() > CHUNK_SIZE {
|
while lst.len() > CHUNK_SIZE {
|
||||||
(first, lst) = lst.split_at(CHUNK_SIZE);
|
(first, lst) = lst.split_at(CHUNK_SIZE);
|
||||||
println!("[{}] [{}]", first, lst);
|
|
||||||
rope.append(first.to_owned());
|
rope.append(first.to_owned());
|
||||||
}
|
}
|
||||||
|
rope.append(lst.to_owned());
|
||||||
rope
|
rope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,15 +239,31 @@ impl From<String> for Rope {
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_creates_a_rope_from_a_string() {
|
fn it_creates_a_rope_from_a_string() {
|
||||||
let content =
|
let test_cases = vec![
|
||||||
|
TestCase{ content: "".to_owned() },
|
||||||
|
TestCase{ content: "This".to_owned() },
|
||||||
|
TestCase{ content: "This is some basic".to_owned() },
|
||||||
|
TestCase{ content: "This is some basic context which is much smaller".to_owned() },
|
||||||
|
TestCase{ content:
|
||||||
"This is some basic context which is much smaller than the rope is designed for."
|
"This is some basic context which is much smaller than the rope is designed for."
|
||||||
.to_owned();
|
.to_owned()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let rope = Rope::from(content.clone());
|
for case in test_cases {
|
||||||
|
let rope = Rope::from(case.content.clone());
|
||||||
|
|
||||||
assert_eq!(rope.to_string(), content);
|
for (idx, node) in rope.contents.iter().enumerate() {
|
||||||
assert_eq!(rope.len(), content.len());
|
}
|
||||||
|
|
||||||
|
assert_eq!(rope.len(), case.content.len(), "{}", case.content);
|
||||||
|
assert_eq!(rope.to_string(), case.content, "{:?}", case.content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue