Skip to content

Commit

Permalink
Proofread: doc and location methods
Browse files Browse the repository at this point in the history
  • Loading branch information
justinpombrio committed Mar 29, 2024
1 parent e7e1f9c commit cf4f311
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 66 deletions.
80 changes: 46 additions & 34 deletions src/engine/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ pub enum DocError {
#[error("No node there to delete")]
CannotDeleteNode,
#[error("Cannot insert that node here")]
CannotInsert,
CannotInsertNode,
}

struct Doc {
pub struct Doc {
cursor: Location,
recent: Option<UndoGroup>,
undo_stack: Vec<UndoGroup>,
Expand All @@ -64,7 +64,7 @@ impl Doc {
self.cursor.bookmark()
}

/// Move the cursor to the bookmark's location.
/// Move the cursor to the bookmark's location, if it's in this document.
pub fn goto_bookmark(&mut self, bookmark: Bookmark, s: &Storage) -> Result<(), DocError> {
if let Some(new_loc) = self.cursor.validate_bookmark(bookmark, s) {
self.cursor = new_loc;
Expand All @@ -74,14 +74,36 @@ impl Doc {
}
}

/// Executes a single command. Clears the redo stack if it was an editing command (but not if
/// it was a navigation command).
pub fn execute(&mut self, cmd: DocCommand, s: &mut Storage) -> Result<(), DocError> {
match cmd {
DocCommand::Ed(cmd) => {
self.redo_stack.clear();
let restore_loc = self.cursor;
let undos = execute_ed(cmd, &mut self.cursor, s)?;
if let Some(recent) = &mut self.recent {
recent.commands.extend(undos);
} else {
self.recent = Some(UndoGroup::new(restore_loc, undos));
}
Ok(())
}
DocCommand::Nav(cmd) => execute_nav(cmd, &mut self.cursor, s),
}
}

/// Groups together all editing commands that have been `.execute()`ed since the last call to
/// `.end_undo_group()`. They will be treated as a single unit ("undo group") by calls to
/// `.undo()` and `.redo()`.
pub fn end_undo_group(&mut self) {
if let Some(recent) = self.recent.take() {
self.undo_stack.push(recent);
}
}

/// Undoes the last undo group on the undo stack and moves it to the redo stack.
/// Returns DocError::NothingToUndo if the undo stack is empty.
/// Returns `Err(DocError::NothingToUndo)` if the undo stack is empty.
/// If there were recent edits _not_ completed with a call to end_undo_group(),
/// the group is automatically ended and then undone.
pub fn undo(&mut self, s: &mut Storage) -> Result<(), DocError> {
Expand Down Expand Up @@ -111,23 +133,6 @@ impl Doc {
Err(DocError::NothingToRedo)
}
}

pub fn execute(&mut self, cmd: DocCommand, s: &mut Storage) -> Result<(), DocError> {
match cmd {
DocCommand::Ed(cmd) => {
self.redo_stack.clear();
let restore_loc = self.cursor;
let undos = execute_ed(cmd, &mut self.cursor, s)?;
if let Some(recent) = &mut self.recent {
recent.commands.extend(undos);
} else {
self.recent = Some(UndoGroup::new(restore_loc, undos));
}
Ok(())
}
DocCommand::Nav(cmd) => execute_nav(cmd, &mut self.cursor, s),
}
}
}

impl UndoGroup {
Expand All @@ -140,17 +145,17 @@ impl UndoGroup {
}

fn execute(self, cursor: &mut Location, s: &mut Storage) -> UndoGroup {
let undo_restore_loc = self.restore_loc;
let mut redo_restore_loc = None;
let mut redos = Vec::new();
for (loc, cmd) in self.commands {
for (loc, cmd) in self.commands.into_iter().rev() {
if redo_restore_loc.is_none() {
redo_restore_loc = Some(loc);
}
jump_to(cursor, loc, s);
redos.extend(execute_ed(cmd, cursor, s).bug_msg("Failed to undo/redo"));
}
jump_to(cursor, undo_restore_loc, s);

jump_to(cursor, self.restore_loc, s);
UndoGroup::new(redo_restore_loc.bug(), redos)
}
}
Expand Down Expand Up @@ -188,13 +193,17 @@ fn execute_tree_ed(
) -> Result<Vec<(Location, EdCommand)>, DocError> {
use TreeEdCommand::*;

if cursor.mode() != Mode::Tree {
return Err(DocError::NotInTreeMode);
}

match cmd {
Insert(node) => match cursor.insert(node, s) {
Ok(None) => Ok(vec![(*cursor, Backspace.into())]),
Ok(Some(detached_node)) => {
Ok(vec![(cursor.prev(s).bug(), Insert(detached_node).into())])
}
Err(()) => Err(DocError::CannotDeleteNode),
Err(()) => Err(DocError::CannotInsertNode),
},
Backspace => {
if let Some(old_node) = cursor.delete_neighbor(true, s) {
Expand All @@ -218,31 +227,32 @@ fn execute_text_ed(
cursor: &mut Location,
s: &mut Storage,
) -> Result<Vec<(Location, EdCommand)>, DocError> {
let original_loc = *cursor;
use TextEdCommand::{Backspace, Delete, Insert};

let (node, char_index) = cursor.text_pos_mut().ok_or(DocError::NotInTextMode)?;
let text = node.text_mut(s).bug();

match cmd {
TextEdCommand::Insert(ch) => {
Insert(ch) => {
text.insert(*char_index, ch);
*char_index += 1;
Ok(vec![(*cursor, TextEdCommand::Backspace.into())])
Ok(vec![(*cursor, Backspace.into())])
}
TextEdCommand::Backspace => {
Backspace => {
if *char_index == 0 {
return Err(DocError::CannotDeleteChar);
}
let ch = text.delete(*char_index - 1);
*char_index -= 1;
Ok(vec![(*cursor, TextEdCommand::Insert(ch).into())])
Ok(vec![(*cursor, Insert(ch).into())])
}
TextEdCommand::Delete => {
Delete => {
let text_len = text.num_chars();
if *char_index == text_len {
return Err(DocError::CannotDeleteChar);
}
let ch = text.delete(*char_index);
Ok(vec![(*cursor, TextEdCommand::Insert(ch).into())])
Ok(vec![(*cursor, Insert(ch).into())])
}
}
}
Expand All @@ -269,7 +279,9 @@ fn execute_tree_nav(
.and_then(|node| Location::after_children(node, s)),
InorderNext => cursor.inorder_next(s),
InorderPrev => cursor.inorder_prev(s),
EnterText => cursor.enter_text(s),
EnterText => cursor
.left_node(s)
.and_then(|node| Location::end_of_text(node, s)),
};

if let Some(new_loc) = new_loc {
Expand Down Expand Up @@ -301,7 +313,7 @@ fn execute_text_nav(
if *char_index >= text.num_chars() {
return Err(DocError::CannotMove);
}
*char_index -= 1;
*char_index += 1;
}
Beginning => *char_index = 0,
End => *char_index = text.num_chars(),
Expand Down
90 changes: 58 additions & 32 deletions src/tree/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ impl Location {
}
}

/// If the node is texty, returns the location at the start of its text, otherwise returns `None`.
pub fn start_of_text(node: Node, s: &Storage) -> Option<Location> {
if node.is_texty(s) {
Some(Location(LocationInner::InText(node, 0)))
} else {
None
}
}

/// If the node is texty, returns the location at the end of its text, otherwise returns `None`.
pub fn end_of_text(node: Node, s: &Storage) -> Option<Location> {
let text_len = node.text(s)?.num_chars();
Some(Location(LocationInner::InText(node, text_len)))
}

/*************
* Accessors *
*************/
Expand Down Expand Up @@ -104,21 +119,18 @@ impl Location {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self.0 {
InText(_, _) => None,
AfterNode(node) => Some(Location::before(node, s)),
BeforeNode(_) => None,
BelowNode(_) => None,
InText(_, _) | BeforeNode(_) | BelowNode(_) => None,
}
}

pub fn next(self, s: &Storage) -> Option<Location> {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self.0 {
InText(_, _) => None,
AfterNode(node) => Some(Location::after(node.next_sibling(s)?, s)),
BeforeNode(node) => Some(Location::after(node, s)),
BelowNode(_) => None,
InText(_, _) | BelowNode(_) => None,
}
}

Expand Down Expand Up @@ -176,19 +188,6 @@ impl Location {
}
}

/// Returns the location at the end of the texty node that is before the current location.
pub fn enter_text(self, s: &Storage) -> Option<Location> {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self.0 {
AfterNode(node) => {
let text_len = node.text(s)?.num_chars();
Some(Location(LocationInner::InText(node, text_len)))
}
InText(_, _) | BeforeNode(_) | BelowNode(_) => None,
}
}

/// If the location is in text, returns the location after that text node.
pub fn exit_text(self) -> Option<Location> {
if let LocationInner::InText(node, _) = self.0 {
Expand All @@ -207,21 +206,18 @@ impl Location {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self.0 {
InText(_, _) => None,
AfterNode(node) => Some(node),
BeforeNode(_) => None,
BelowNode(_) => None,
InText(_, _) | BeforeNode(_) | BelowNode(_) => None,
}
}

pub fn right_node(self, s: &Storage) -> Option<Node> {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self.0 {
InText(_, _) => None,
AfterNode(node) => node.next_sibling(s),
BeforeNode(node) => Some(node),
BelowNode(_) => None,
InText(_, _) | BelowNode(_) => None,
}
}

Expand All @@ -230,20 +226,30 @@ impl Location {

match self.0 {
InText(node, _) => None,
AfterNode(node) => node.parent(s),
BeforeNode(node) => node.parent(s),
BeforeNode(node) | AfterNode(node) => node.parent(s),
BelowNode(node) => Some(node),
}
}

pub fn root_node(self, s: &Storage) -> Node {
self.0.node().root(s)
self.0.reference_node().root(s)
}

/************
* Mutation *
************/

/// In a listy sequence, inserts `new_node` at this location and returns `Ok(None)`. In a fixed
/// sequence, replaces the node after this location with `new_node` and returns
/// `Ok(Some(old_node))`. Either way, moves `self` to after the new node.
///
/// If we cannot insert, returns `Err(())` and does not modify `self`. This can happen for any
/// of the following reasons:
///
/// - This location is in text.
/// - This location is before or after a root node.
/// - This location is after the last node in a fixed sequence.
/// - The new node does not match the required sort.
#[allow(clippy::result_unit_err)]
pub fn insert(&mut self, new_node: Node, s: &mut Storage) -> Result<Option<Node>, ()> {
use LocationInner::*;
Expand All @@ -263,7 +269,7 @@ impl Location {
}
Arity::Listy(_) => {
let success = match self.0 {
InText(_, _) => false,
InText(_, _) => bug!("insert: bug in textiness check"),
AfterNode(left_node) => left_node.insert_after(s, new_node),
BeforeNode(right_node) => right_node.insert_before(s, new_node),
BelowNode(_) => parent.insert_last_child(s, new_node),
Expand All @@ -278,25 +284,43 @@ impl Location {
}
}

/// In a listy sequence, delete the node before (after) the cursor. In a fixed sequence,
/// replace the node before (after) the cursor with a hole, and move the cursor before (after)
/// it.
#[must_use]
pub fn delete_neighbor(&mut self, on_left: bool, s: &mut Storage) -> Option<Node> {
pub fn delete_neighbor(&mut self, delete_before: bool, s: &mut Storage) -> Option<Node> {
let parent = self.parent_node(s)?;
let node = if on_left {
let node = if delete_before {
self.left_node(s)?
} else {
self.right_node(s)?
};

match parent.arity(s) {
Arity::Fixed(_) => {
let hole = Node::new_hole(s, parent.language(s));
// NOTE: Think about what language the hole should be in once we start supporting
// multi-language docs
let hole = Node::new_hole(s, node.language(s));
if node.swap(s, hole) {
*self = if delete_before {
Location::before(hole, s)
} else {
Location::after(hole, s)
};
Some(node)
} else {
None
}
}
Arity::Listy(_) => {
let prev_node = node.prev_sibling(s);
let next_node = node.next_sibling(s);
if node.detach(s) {
*self = match (prev_node, next_node) {
(Some(prev), _) => Location(LocationInner::AfterNode(prev)),
(None, Some(next)) => Location(LocationInner::BeforeNode(next)),
(None, None) => Location(LocationInner::BelowNode(parent)),
};
Some(node)
} else {
None
Expand All @@ -322,7 +346,7 @@ impl Location {
/// has since been deleted, or if it is currently located in a
/// different tree.
pub fn validate_bookmark(self, mark: Bookmark, s: &Storage) -> Option<Location> {
let mark_node = mark.0.node();
let mark_node = mark.0.reference_node();
if mark_node.is_valid(s) && mark_node.root(s) == self.root_node(s) {
Some(Location(mark.0.normalize(s)))
} else {
Expand All @@ -346,7 +370,9 @@ impl LocationInner {
}
}

fn node(self) -> Node {
/// Get the node this location is defined relative to. May be before, after, or above this
/// location!
fn reference_node(self) -> Node {
use LocationInner::{AfterNode, BeforeNode, BelowNode, InText};

match self {
Expand Down

0 comments on commit cf4f311

Please sign in to comment.