From 119bce59309e00c64bca1152b174e2ceaf7592b0 Mon Sep 17 00:00:00 2001 From: Justin Date: Sun, 19 May 2024 11:00:14 -0400 Subject: [PATCH] Change cursor type! Cursor is at a node; lists have an extra loc at end. --- scripts/init.rhai | 10 +- src/engine/command.rs | 50 ++++--- src/engine/doc.rs | 53 +++---- src/engine/doc_set.rs | 2 +- src/engine/mod.rs | 2 +- src/pretty_doc.rs | 24 +--- src/runtime.rs | 29 +--- src/style.rs | 23 +-- src/tree/location.rs | 317 +++++++++++++++++------------------------- src/tree/node.rs | 14 ++ tests/keyhint.rs | 1 + tests/selection.rs | 1 + tests/tests.rs | 2 +- 13 files changed, 221 insertions(+), 307 deletions(-) diff --git a/scripts/init.rhai b/scripts/init.rhai index 7496e22..f0c3625 100644 --- a/scripts/init.rhai +++ b/scripts/init.rhai @@ -70,10 +70,11 @@ tree_keymap.bind_key("k", "Prev", || s::tree_nav_prev()); tree_keymap.bind_key("K", "First", || s::tree_nav_first()); tree_keymap.bind_key("j", "Next", || s::tree_nav_next()); tree_keymap.bind_key("J", "Last", || s::tree_nav_last()); -tree_keymap.bind_key("h", "ChildLeft", || s::tree_nav_child_left()); -tree_keymap.bind_key("H", "ParentLeft", || s::tree_nav_before_parent()); -tree_keymap.bind_key("l", "ChildRight", || s::tree_nav_child_right()); -tree_keymap.bind_key("L", "ParentRight", || s::tree_nav_after_parent()); +tree_keymap.bind_key("l", "FirstChild", || s::tree_nav_first_child()); +tree_keymap.bind_key("L", "LastChild", || s::tree_nav_last_child()); +tree_keymap.bind_key("h", "Parent", || s::tree_nav_parent()); +tree_keymap.bind_key("tab", "NextLeaf", || s::tree_nav_next_leaf()); +tree_keymap.bind_key("S-tab", "PrevLeaf", || s::tree_nav_prev_leaf()); tree_keymap.bind_key(";", "Parent", || s::tree_nav_parent()); tree_keymap.bind_key("^", "First", || s::tree_nav_first()); @@ -109,6 +110,7 @@ tree_keymap.bind_key("i", "QuickInsert", || { let text_keymap = new_keymap(); text_keymap.bind_key("esc", "ExitText", || s::text_nav_exit()); +text_keymap.bind_key("enter", "ExitText", || s::text_nav_exit()); text_keymap.bind_key("left", "Left", || s::text_nav_left()); text_keymap.bind_key("right", "Right", || s::text_nav_right()); text_keymap.bind_key("bksp", "Backspace", || s::text_ed_backspace()); diff --git a/src/engine/command.rs b/src/engine/command.rs index 3f8dbde..21e3d98 100644 --- a/src/engine/command.rs +++ b/src/engine/command.rs @@ -23,17 +23,16 @@ pub enum NavCommand { #[derive(Debug)] pub enum TreeEdCommand { - /// In a listy sequence, insert the given node at the cursor position. In a fixed sequence, - /// replace the node after the cursor with the given node. Either way, move the cursor after - /// the new node. + /// In a listy sequence, insert the given node before the cursor. In a fixed sequence, replace + /// the node at the cursor with the given node. Either way, move the cursor to the new node. Insert(Node), - /// Replace the node to the left of the cursor with the given node. + /// Replace the node at the cursor with the given node. Replace(Node), - /// In a listy sequence, delete the node before the cursor. In a fixed sequence, - /// replace the node before the cursor with a hole, and move the cursor before it. + /// In a listy sequence, delete the node at the cursor and move the cursor to the left. In a + /// fixed sequence, replace the node at the cursor with a hole. Backspace, - /// In a listy sequence, delete the node after the cursor. In a fixed sequence, - /// replace the node after the cursor with a hole, and move the cursor after it. + /// In a listy sequence, delete the node at the cursor and move the cursor to the right. In a + /// fixed sequence, replace the node at the cursor with a hole. Delete, } @@ -51,12 +50,12 @@ pub enum TextEdCommand { // TODO: cut=copy,backspace paste-copy=dup,paste #[derive(Debug)] pub enum ClipboardCommand { - /// Copy the node to the left of the cursor and push it onto the clipboard stack. + /// Copy the node at the cursor and push it onto the clipboard stack. Copy, /// Pop the top node from the clipboard stack, and insert it at the cursor (in the same manner /// as [`TreeEdCommand::Insert`]). Paste, - /// Swap the top node in the clipboard stack with the node to the left of the cursor. + /// Swap the top node in the clipboard stack with the node at the cursor. PasteSwap, /// Duplicate the top node in the clipboard stack. Dup, @@ -69,28 +68,27 @@ pub enum ClipboardCommand { pub enum TreeNavCommand { /// Move the cursor back one node. Prev, - /// Move the cursor to before the first sibling. + /// Move the cursor to the first sibling. First, /// Move the cursor forward one node. Next, - /// Move the cursor to after the last sibling. + /// Move the cursor to the last sibling, or after it in a listy sequence. Last, - /// Move the cursor to the next location in-order. - InorderNext, - /// Move the cursor to the previous location in-order. - InorderPrev, - /// Move the cursor after its parent. - AfterParent, - /// Move the cursor before its parent. - BeforeParent, - /// Move the cursor to before the first child of the node after the cursor. - ChildRight, - /// Move the cursor to after the last child of the node before the cursor. - ChildLeft, - /// If the node before the cursor is texty, enter text mode, placing the cursor at the + /// Move the cursor to its parent node. + Parent, + /// Move the cursor to the first child of the node at the cursor. + FirstChild, + /// Move the cursor to the last child of the node at the cursor, or after it in a listy + /// sequence. + LastChild, + /// Move the cursor to the next leaf node (node with no children). + NextLeaf, + /// Move the cursor to the previous leaf node (node with no children). + PrevLeaf, + /// If the node at the cursor is texty, enter text mode, placing the cursor at the /// end of the text. EnterText, - /// Use this when the node before the cursor has just been `Insert`ed, to move the cursor to a + /// Use this when the node at the cursor has just been `Insert`ed, to move the cursor to a /// convenient editing location. FirstInsertLoc, } diff --git a/src/engine/doc.rs b/src/engine/doc.rs index daacc51..78a1d18 100644 --- a/src/engine/doc.rs +++ b/src/engine/doc.rs @@ -64,7 +64,8 @@ impl Doc { return None; } Some(Doc { - cursor: Location::before_children(s, root_node).bug(), + cursor: Location::before_children(s, root_node) + .bug_msg("Root constructs must be able to have at least 1 child"), recent: None, undo_stack: Vec::new(), redo_stack: Vec::new(), @@ -256,32 +257,25 @@ fn execute_tree_ed( match cmd { Insert(node) => match cursor.insert(s, node) { - Ok(None) => Ok(vec![(*cursor, Backspace.into())]), - Ok(Some(detached_node)) => Ok(vec![( - cursor.prev_sibling(s).bug(), - Insert(detached_node).into(), - )]), + Ok(None) => Ok(vec![(*cursor, Delete.into())]), + Ok(Some(detached_node)) => Ok(vec![(*cursor, Insert(detached_node).into())]), Err(()) => Err(EditError::CannotPlaceNode), }, Replace(new_node) => { - let old_node = cursor.left_node(s).ok_or(EditError::NoNodeHere)?; + let old_node = cursor.node(s).ok_or(EditError::NoNodeHere)?; if old_node.swap(s, new_node) { - *cursor = Location::after(s, new_node); + *cursor = Location::at(s, new_node); Ok(vec![(*cursor, Replace(old_node).into())]) } else { Err(EditError::CannotPlaceNode) } } Backspace => { - let (old_node, undo_location) = cursor - .delete_neighbor(s, true) - .ok_or(EditError::NoNodeHere)?; + let (old_node, undo_location) = cursor.delete(s, true).ok_or(EditError::NoNodeHere)?; Ok(vec![(undo_location, Insert(old_node).into())]) } Delete => { - let (old_node, undo_location) = cursor - .delete_neighbor(s, false) - .ok_or(EditError::NoNodeHere)?; + let (old_node, undo_location) = cursor.delete(s, false).ok_or(EditError::NoNodeHere)?; Ok(vec![(undo_location, Insert(old_node).into())]) } } @@ -332,7 +326,7 @@ fn execute_clipboard( match cmd { Copy => { - let node = cursor.left_node(s).ok_or(EditError::NoNodeHere)?; + let node = cursor.node(s).ok_or(EditError::NoNodeHere)?; clipboard.push(node.deep_copy(s)); Ok(Vec::new()) } @@ -346,9 +340,9 @@ fn execute_clipboard( } PasteSwap => { let clip_node = clipboard.pop().ok_or(EditError::EmptyClipboard)?; - let doc_node = cursor.right_node(s).ok_or(EditError::NoNodeHere)?; + let doc_node = cursor.node(s).ok_or(EditError::NoNodeHere)?; if doc_node.swap(s, clip_node) { - *cursor = Location::after(s, clip_node); + *cursor = Location::at(s, clip_node); clipboard.push(doc_node.deep_copy(s)); Ok(vec![(*cursor, TreeEdCommand::Replace(doc_node).into())]) } else { @@ -383,23 +377,22 @@ fn execute_tree_nav( let new_loc = match cmd { Prev => cursor.prev_cousin(s), Next => cursor.next_cousin(s), - First => cursor.first(s), - Last => cursor.last(s), - BeforeParent => cursor.before_parent(s), - AfterParent => cursor.after_parent(s), - ChildLeft => cursor - .left_node(s) - .and_then(|node| Location::after_children(s, node)), - ChildRight => cursor - .right_node(s) + First => cursor.first_sibling(s), + Last => cursor.last_sibling(s), + PrevLeaf => cursor.prev_leaf(s), + NextLeaf => cursor.next_leaf(s), + Parent => cursor.parent(s), + FirstChild => cursor + .node(s) .and_then(|node| Location::before_children(s, node)), - InorderNext => cursor.inorder_next(s), - InorderPrev => cursor.inorder_prev(s), + LastChild => cursor + .node(s) + .and_then(|node| Location::after_children(s, node)), EnterText => cursor - .left_node(s) + .node(s) .and_then(|node| Location::end_of_text(s, node)), FirstInsertLoc => cursor - .left_node(s) + .node(s) .map(|node| Location::first_insert_loc(s, node)), }; diff --git a/src/engine/doc_set.rs b/src/engine/doc_set.rs index c502923..95f8e86 100644 --- a/src/engine/doc_set.rs +++ b/src/engine/doc_set.rs @@ -156,7 +156,7 @@ impl DocSet { focus_target, focus_height: settings.focus_height, width_strategy: pane::WidthStrategy::NoMoreThan(settings.max_display_width), - set_focus: true, + set_focus: doc.cursor().node(s).is_none(), }; (doc, options) } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 6535e0c..f0f692b 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -24,7 +24,7 @@ impl Default for Settings { Settings { max_source_width: 100, max_display_width: 120, - focus_height: 0.5, + focus_height: 0.25, } } } diff --git a/src/pretty_doc.rs b/src/pretty_doc.rs index 85a2784..2f81a78 100644 --- a/src/pretty_doc.rs +++ b/src/pretty_doc.rs @@ -1,6 +1,6 @@ use crate::language::Storage; use crate::style::{ - Condition, Style, StyleLabel, ValidNotation, HOLE_STYLE, LEFT_CURSOR_STYLE, RIGHT_CURSOR_STYLE, + Condition, Style, StyleLabel, ValidNotation, CLOSE_STYLE, CURSOR_STYLE, HOLE_STYLE, }; use crate::tree::{Location, Node, NodeId}; use crate::util::{error, SynlessBug, SynlessError}; @@ -90,20 +90,12 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> { fn lookup_style(self, style_label: StyleLabel) -> Result { Ok(match style_label { StyleLabel::Hole => HOLE_STYLE, - StyleLabel::Open => { - let parent = self.cursor_loc.parent_node(self.storage); - let left = self.cursor_loc.left_node(self.storage); - if parent == Some(self.node) && left.is_none() { - LEFT_CURSOR_STYLE - } else { - Style::default() - } - } + StyleLabel::Open => Style::default(), StyleLabel::Close => { let parent = self.cursor_loc.parent_node(self.storage); - let right = self.cursor_loc.right_node(self.storage); - if parent == Some(self.node) && right.is_none() { - RIGHT_CURSOR_STYLE + let node_at_cursor = self.cursor_loc.node(self.storage); + if parent == Some(self.node) && node_at_cursor.is_none() { + CLOSE_STYLE } else { Style::default() } @@ -126,10 +118,8 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> { } fn node_style(self) -> Result { - let style = if self.cursor_loc.left_node(self.storage) == Some(self.node) { - LEFT_CURSOR_STYLE - } else if self.cursor_loc.right_node(self.storage) == Some(self.node) { - RIGHT_CURSOR_STYLE + let style = if self.cursor_loc.node(self.storage) == Some(self.node) { + CURSOR_STYLE } else { Style::default() }; diff --git a/src/runtime.rs b/src/runtime.rs index 1ff9613..d000bd0 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -359,7 +359,7 @@ impl + 'static> Runtime { pub fn cut(&mut self) -> Result<(), SynlessError> { self.engine.execute(ClipboardCommand::Copy)?; - self.engine.execute(TreeEdCommand::Backspace) + self.engine.execute(TreeEdCommand::Delete) } /*********** @@ -567,29 +567,12 @@ impl + 'static> Runtime { register!( module, rt, - TreeNavCommand::InorderNext as tree_nav_inorder_next - ); - register!( - module, - rt, - TreeNavCommand::InorderPrev as tree_nav_inorder_prev - ); - register!( - module, - rt, - TreeNavCommand::ChildRight as tree_nav_child_right - ); - register!(module, rt, TreeNavCommand::ChildLeft as tree_nav_child_left); - register!( - module, - rt, - TreeNavCommand::BeforeParent as tree_nav_before_parent - ); - register!( - module, - rt, - TreeNavCommand::AfterParent as tree_nav_after_parent + TreeNavCommand::FirstChild as tree_nav_first_child ); + register!(module, rt, TreeNavCommand::PrevLeaf as tree_nav_prev_leaf); + register!(module, rt, TreeNavCommand::NextLeaf as tree_nav_next_leaf); + register!(module, rt, TreeNavCommand::LastChild as tree_nav_last_child); + register!(module, rt, TreeNavCommand::Parent as tree_nav_parent); register!(module, rt, TreeNavCommand::EnterText as tree_nav_enter_text); // Editing: Tree Ed diff --git a/src/style.rs b/src/style.rs index 8411c28..fa1e9cb 100644 --- a/src/style.rs +++ b/src/style.rs @@ -9,21 +9,21 @@ pub const HOLE_STYLE: Style = Style { ..Style::const_default() }; -pub const LEFT_CURSOR_STYLE: Style = Style { +pub const CLOSE_STYLE: Style = Style { cursor: Some(CursorHalf::Left), - bg_color: Some((Base16Color::Base02, Priority::High)), + fg_color: Some((Base16Color::Base00, Priority::High)), + bg_color: Some((Base16Color::Base04, Priority::High)), ..Style::const_default() }; -pub const RIGHT_CURSOR_STYLE: Style = Style { - cursor: Some(CursorHalf::Right), - bg_color: Some((Base16Color::Base00, Priority::High)), +pub const CURSOR_STYLE: Style = Style { + cursor: Some(CursorHalf::Left), + bg_color: Some((Base16Color::Base02, Priority::High)), ..Style::const_default() }; pub const FG_COLOR: Base16Color = Base16Color::Base05; -// NOTE: we might want to use Base00 as the default background, to follow the base16 conventions. -pub const BG_COLOR: Base16Color = Base16Color::Base01; +pub const BG_COLOR: Base16Color = Base16Color::Base00; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ConcreteStyle { @@ -233,12 +233,13 @@ impl Style { } impl ColorTheme { - /// The "default dark" Base16 colorscheme, by Chris Kempson (http://chriskempson.com) + /// The "default dark" Base16 colorscheme, by Chris Kempson + /// [link](https://github.com/chriskempson/base16-default-schemes) pub fn default_dark() -> ColorTheme { ColorTheme { - base00: Rgb::from_hex("#103030").bug(), - base01: Rgb::from_hex("#111111").bug(), - base02: Rgb::from_hex("#312121").bug(), + base00: Rgb::from_hex("#181818").bug(), + base01: Rgb::from_hex("#282828").bug(), + base02: Rgb::from_hex("#383838").bug(), base03: Rgb::from_hex("#585858").bug(), base04: Rgb::from_hex("#b8b8b8").bug(), base05: Rgb::from_hex("#d8d8d8").bug(), diff --git a/src/tree/location.rs b/src/tree/location.rs index 0597ab4..31631d8 100644 --- a/src/tree/location.rs +++ b/src/tree/location.rs @@ -13,20 +13,15 @@ pub struct Bookmark(LocationInner); #[derive(Debug, Clone, Copy)] pub struct Location(LocationInner); -/// This data type admits multiple representations of the same location. For example, a location -/// between nodes X and Y could be represented as either `AfterNode(X)` or `BeforeNode(Y)`. We -/// therefore keep locations in a _normal form_. The exception is Bookmarks, which might not be in -/// normal form (or even valid!) and must be checked and normalized before use. The rule for the -/// normal form is that `AfterNode` is used if possible, falling back to `BeforeNode` and then -/// `BelowNode`. This implies that `BelowNode` is only used in empty sequences. #[derive(Debug, Clone, Copy)] enum LocationInner { /// The usize is an index between chars (so it can be equal to the len) InText(Node, usize), - AfterNode(Node), - BeforeNode(Node), + AtNode(Node), + /// After the last child of `Node`; requires that `Node` be listy. BelowNode(Node), } +use LocationInner::{AtNode, BelowNode, InText}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Mode { @@ -39,45 +34,37 @@ impl Location { * Constructors * ****************/ - pub fn before(s: &Storage, node: Node) -> Location { - Location(LocationInner::BeforeNode(node).normalize(s)) + pub fn at(_s: &Storage, node: Node) -> Location { + Location(AtNode(node)) } - pub fn after(_s: &Storage, node: Node) -> Location { - // already normal form - Location(LocationInner::AfterNode(node)) - } - - /// Returns the location at the beginning of the child sequence of the given node. - /// (Returns `None` for a texty node, or a fixed node with no children.) + /// Returns the left-most location in the node's child sequence. + /// (Returns `None` for a texty node or a fixed node with no children.) pub fn before_children(s: &Storage, node: Node) -> Option { if !node.can_have_children(s) { return None; } if let Some(first_child) = node.first_child(s) { - Some(Location::before(s, first_child)) + Some(Location(AtNode(first_child))) } else { - Some(Location(LocationInner::BelowNode(node))) + Some(Location(BelowNode(node))) } } - /// Returns the location at the end of the child sequence of the given node. - /// (Returns `None` for a texty node, or a fixed node with no children.) + /// Returns the right-most location in the node's child sequence. + /// (Returns `None` for a texty node or a fixed node with no children.) pub fn after_children(s: &Storage, node: Node) -> Option { - if !node.can_have_children(s) { - return None; - } - if let Some(last_child) = node.last_child(s) { - Some(Location::after(s, last_child)) - } else { - Some(Location(LocationInner::BelowNode(node))) + match node.arity(s) { + Arity::Texty => None, + Arity::Fixed(_) => node.last_child(s).map(|child| Location(AtNode(child))), + Arity::Listy(_) => Some(Location(BelowNode(node))), } } /// If the node is texty, returns the location at the start of its text, otherwise returns `None`. pub fn start_of_text(s: &Storage, node: Node) -> Option { if node.is_texty(s) { - Some(Location(LocationInner::InText(node, 0))) + Some(Location(InText(node, 0))) } else { None } @@ -86,25 +73,19 @@ impl Location { /// If the node is texty, returns the location at the end of its text, otherwise returns `None`. pub fn end_of_text(s: &Storage, node: Node) -> Option { let text_len = node.text(s)?.num_chars(); - Some(Location(LocationInner::InText(node, text_len))) + Some(Location(InText(node, text_len))) } /// Where to move the cursor after inserting this node. pub fn first_insert_loc(s: &Storage, node: Node) -> Location { - Location::first_insert_loc_impl(s, node, true) - } - - fn first_insert_loc_impl(s: &Storage, node: Node, top_level: bool) -> Location { match node.arity(s) { Arity::Texty => Location::end_of_text(s, node).bug(), Arity::Listy(_) => Location::before_children(s, node).bug(), Arity::Fixed(_) => { if let Some(child) = node.first_child(s) { - Location::first_insert_loc_impl(s, child, false) - } else if top_level { - Location::after(s, node) + Location::first_insert_loc(s, child) } else { - Location::before(s, node) + Location(AtNode(node)) } } } @@ -116,13 +97,13 @@ impl Location { pub fn mode(self) -> Mode { match self.0 { - LocationInner::InText(_, _) => Mode::Text, + InText(_, _) => Mode::Text, _ => Mode::Tree, } } pub fn text_pos(self) -> Option<(Node, usize)> { - if let LocationInner::InText(node, char_pos) = self.0 { + if let InText(node, char_pos) = self.0 { Some((node, char_pos)) } else { None @@ -130,7 +111,7 @@ impl Location { } pub fn text_pos_mut(&mut self) -> Option<(Node, &mut usize)> { - if let LocationInner::InText(node, char_pos) = &mut self.0 { + if let InText(node, char_pos) = &mut self.0 { Some((*node, char_pos)) } else { None @@ -140,14 +121,17 @@ impl Location { /// Find a path from the root node to a node near this location, together with /// a `FocusTarget` specifying where this location is relative to that node. pub fn path_from_root(self, s: &Storage) -> (Vec, ppp::FocusTarget) { - use LocationInner::*; - let mut path_to_root = Vec::new(); let (mut node, target) = match self.0 { - BeforeNode(node) => (node, ppp::FocusTarget::Start), - AfterNode(node) => (node, ppp::FocusTarget::End), - // NOTE: This relies on the node's notation containing a `Notation::FocusMark`. - BelowNode(node) => (node, ppp::FocusTarget::Mark), + BelowNode(node) => { + if let Some(last_child) = node.last_child(s) { + (last_child, ppp::FocusTarget::End) + } else { + // NOTE: This relies on the node's notation containing a `Notation::FocusMark`. + (node, ppp::FocusTarget::Mark) + } + } + AtNode(node) => (node, ppp::FocusTarget::Start), InText(node, char_pos) => (node, ppp::FocusTarget::Text(char_pos)), }; while let Some(parent) = node.parent(s) { @@ -167,146 +151,117 @@ impl Location { **************/ pub fn prev_cousin(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - InText(_, _) => return None, - AfterNode(node) => return Some(Location::before(s, node)), - BeforeNode(_) | BelowNode(_) => (), + if matches!(self.0, InText(_, _)) { + return None; + } + if let Some(sibling) = self.prev_sibling(s) { + return Some(sibling); } - Location::after_children(s, self.parent_node(s)?.prev_cousin(s)?) } pub fn next_cousin(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - InText(_, _) => return None, - BeforeNode(node) => return Some(Location::after(s, node)), - AfterNode(node) => { - if let Some(sibling) = node.next_sibling(s) { - return Some(Location::after(s, sibling)); - } - } - BelowNode(_) => (), + if matches!(self.0, InText(_, _)) { + return None; + } + if let Some(sibling) = self.next_sibling(s) { + return Some(sibling); } - Location::before_children(s, self.parent_node(s)?.next_cousin(s)?) } pub fn prev_sibling(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - match self.0 { - AfterNode(node) => Some(Location::before(s, node)), - InText(_, _) | BeforeNode(_) | BelowNode(_) => None, + AtNode(node) => Some(Location(AtNode(node.prev_sibling(s)?))), + BelowNode(parent) => parent.last_child(s).map(|child| Location(AtNode(child))), + InText(_, _) => None, } } pub fn next_sibling(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - AfterNode(node) => Some(Location::after(s, node.next_sibling(s)?)), - BeforeNode(node) => Some(Location::after(s, node)), - InText(_, _) | BelowNode(_) => None, - } - } - - pub fn first(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - InText(_, _) => None, - AfterNode(node) => Some(Location::before(s, node.first_sibling(s))), - BeforeNode(_) | BelowNode(_) => Some(self), + let node = match self.0 { + AtNode(node) => node, + InText(_, _) | BelowNode(_) => return None, + }; + if let Some(sibling) = node.next_sibling(s) { + return Some(Location(AtNode(sibling))); } - } - - pub fn last(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - InText(_, _) => None, - BeforeNode(node) | AfterNode(node) => Some(Location::after(s, node.last_sibling(s))), - BelowNode(_) => Some(self), + let parent = node.parent(s)?; + if matches!(parent.arity(s), Arity::Listy(_)) { + Some(Location(BelowNode(parent))) + } else { + None } } - pub fn before_parent(self, s: &Storage) -> Option { - Some(Location::before(s, self.parent_node(s)?)) + /// Get the left-most location among this node's siblings. + pub fn first_sibling(self, s: &Storage) -> Option { + Location::before_children(s, self.parent_node(s)?) } - pub fn after_parent(self, s: &Storage) -> Option { - Some(Location::after(s, self.parent_node(s)?)) + /// Get the right-most location among this node's siblings. + pub fn last_sibling(self, s: &Storage) -> Option { + Location::after_children(s, self.parent_node(s)?) } - /// Returns the next location in an inorder tree traversal. - pub fn inorder_next(self, s: &Storage) -> Option { - if let Some(right_node) = self.right_node(s) { - if let Some(loc) = Location::before_children(s, right_node) { - Some(loc) - } else { - Some(Location::after(s, right_node)) + /// Get the location at the next "leaf" node (node with no children). + pub fn next_leaf(self, s: &Storage) -> Option { + let mut node = match self.0 { + InText(_, _) => return None, + AtNode(node) => { + if let Some(child) = node.first_child(s) { + return Some(Location(AtNode(child.first_leaf(s)))); + } else { + node + } } - } else { - self.after_parent(s) + BelowNode(node) => node, + }; + while node.next_sibling(s).is_none() { + node = node.parent(s)?; } + Some(Location(AtNode(node.next_sibling(s).bug().first_leaf(s)))) } - /// Returns the previous location in an inorder tree traversal. - pub fn inorder_prev(self, s: &Storage) -> Option { - if let Some(left_node) = self.left_node(s) { - if let Some(loc) = Location::after_children(s, left_node) { - Some(loc) - } else { - Some(Location::before(s, left_node)) - } - } else { - self.before_parent(s) + /// Get the location at the previous "leaf" node (node with no children). + pub fn prev_leaf(mut self, s: &Storage) -> Option { + while self.prev_sibling(s).is_none() { + self = self.parent(s)?; } + Some(Location(AtNode( + self.node(s)?.prev_sibling(s)?.last_leaf(s), + ))) + } + + /// Get the location at this node's parent. + pub fn parent(self, s: &Storage) -> Option { + Some(Location(AtNode(self.parent_node(s)?))) } /// If the location is in text, returns the location after that text node. pub fn exit_text(self) -> Option { - if let LocationInner::InText(node, _) = self.0 { - // already in normal form - Some(Location(LocationInner::AfterNode(node))) + if let InText(node, _) = self.0 { + Some(Location(AtNode(node))) } else { None } } - /********************** - * Navigation to Node * - **********************/ - - pub fn left_node(self, _s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; + /***************** + * Getting Nodes * + *****************/ + pub fn node(self, _s: &Storage) -> Option { match self.0 { - AfterNode(node) => Some(node), - InText(_, _) | BeforeNode(_) | BelowNode(_) => None, - } - } - - pub fn right_node(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - - match self.0 { - AfterNode(node) => node.next_sibling(s), - BeforeNode(node) => Some(node), + AtNode(node) => Some(node), InText(_, _) | BelowNode(_) => None, } } pub fn parent_node(self, s: &Storage) -> Option { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - match self.0 { InText(_, _) => None, - BeforeNode(node) | AfterNode(node) => node.parent(s), + AtNode(node) => node.parent(s), BelowNode(node) => Some(node), } } @@ -319,29 +274,26 @@ impl Location { * 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. + /// In a listy sequence, inserts `new_node` to the left of this location and returns + /// `Ok(None)`. In a fixed sequence, replaces the node at this location with `new_node` and + /// returns `Ok(Some(old_node))`. Either way, moves `self` to 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. + /// - This location is at the root. /// - The new node does not match the required sort. #[allow(clippy::result_unit_err)] pub fn insert(&mut self, s: &mut Storage, new_node: Node) -> Result, ()> { - use LocationInner::*; - let parent = self.parent_node(s).ok_or(())?; match parent.arity(s) { Arity::Texty => bug!("insert: texty parent"), Arity::Fixed(_) => { - let old_node = self.right_node(s).ok_or(())?; + let old_node = self.node(s).ok_or(())?; if new_node.swap(s, old_node) { - *self = Location::after(s, new_node); + *self = Location(AtNode(new_node)); Ok(Some(old_node)) } else { Err(()) @@ -350,12 +302,11 @@ impl Location { Arity::Listy(_) => { let success = match self.0 { 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), + AtNode(node) => node.insert_before(s, new_node), BelowNode(_) => parent.insert_last_child(s, new_node), }; if success { - *self = Location::after(s, new_node); + *self = Location(AtNode(new_node)); Ok(None) } else { Err(()) @@ -364,55 +315,41 @@ impl Location { } } - /// In a listy sequence, deletes the node before (after) the cursor. In a fixed sequence, - /// replaces the node before (after) the cursor with a hole, and moves the cursor before (after) - /// it. Returns the node that was deleted and the location where the undo command should be + /// Deletes the node at the cursor. If in a listy sequence, attempts to move the cursor left or + /// right. Returns the node that was deleted and the location where the undo command should be /// executed from. #[must_use] - pub fn delete_neighbor( - &mut self, - s: &mut Storage, - delete_before: bool, - ) -> Option<(Node, Location)> { - let parent = self.parent_node(s)?; - let node = if delete_before { - self.left_node(s)? - } else { - self.right_node(s)? + pub fn delete(&mut self, s: &mut Storage, move_left: bool) -> Option<(Node, Location)> { + let node = match self.0 { + InText(_, _) | BelowNode(_) => return None, + AtNode(node) => node, }; - + let parent = node.parent(s)?; match parent.arity(s) { + Arity::Texty => bug!("texty parent"), Arity::Fixed(_) => { - // 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) { - if delete_before { - *self = Location::before(s, hole); - Some((node, *self)) - } else { - *self = Location::after(s, hole); - Some((node, Location::before(s, hole))) - } + *self = Location(AtNode(hole)); + Some((node, *self)) } else { None } } Arity::Listy(_) => { - let prev_node = node.prev_sibling(s); - let next_node = node.next_sibling(s); + let next_loc = self.next_sibling(s).bug(); + let opt_prev_loc = self.prev_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)), + *self = if move_left { + opt_prev_loc.unwrap_or(next_loc) + } else { + next_loc }; - Some((node, *self)) + Some((node, next_loc)) } else { None } } - Arity::Texty => bug!("delete_neighbor: texty parent"), } } @@ -443,26 +380,20 @@ impl Location { impl LocationInner { fn normalize(self, s: &Storage) -> LocationInner { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - match self { InText(node, i) => { let text_len = node.text(s).bug().num_chars(); InText(node, i.min(text_len)) } - AfterNode(_) => self, - BeforeNode(node) => node.prev_sibling(s).map(AfterNode).unwrap_or(self), - BelowNode(parent) => parent.last_child(s).map(AfterNode).unwrap_or(self), + AtNode(_) | BelowNode(_) => self, } } - /// Get the node this location is defined relative to. May be before, after, or above this + /// Get the node this location is defined relative to. May be at or above this /// location! fn reference_node(self) -> Node { - use LocationInner::{AfterNode, BeforeNode, BelowNode, InText}; - match self { - InText(node, _) | AfterNode(node) | BeforeNode(node) | BelowNode(node) => node, + InText(node, _) | AtNode(node) | BelowNode(node) => node, } } } diff --git a/src/tree/node.rs b/src/tree/node.rs index 797020f..e308a0d 100644 --- a/src/tree/node.rs +++ b/src/tree/node.rs @@ -318,6 +318,20 @@ impl Node { } } + pub fn first_leaf(mut self, s: &Storage) -> Node { + while let Some(child) = self.first_child(s) { + self = child; + } + self + } + + pub fn last_leaf(mut self, s: &Storage) -> Node { + while let Some(child) = self.last_child(s) { + self = child; + } + self + } + pub fn root(self, s: &Storage) -> Node { Node(s.forest().root(self.0)) } diff --git a/tests/keyhint.rs b/tests/keyhint.rs index 8505b03..5e417e7 100644 --- a/tests/keyhint.rs +++ b/tests/keyhint.rs @@ -33,6 +33,7 @@ fn test_keyhint_lang() { let hint_node = Node::with_text(s, c_hint, hint.to_owned()).unwrap(); let entry_node = Node::with_children(s, c_entry, [key_node, hint_node]).unwrap(); cursor.insert(s, entry_node).unwrap(); + *cursor = cursor.next_sibling(s).unwrap(); }; add_entry(s, &mut cursor, "h", "left"); diff --git a/tests/selection.rs b/tests/selection.rs index 07341f7..6ee6457 100644 --- a/tests/selection.rs +++ b/tests/selection.rs @@ -28,6 +28,7 @@ fn test_selection_lang() { let construct = lang.construct(s, construct_name).unwrap(); let node = Node::with_text(s, construct, text.to_owned()).unwrap(); cursor.insert(s, node).unwrap(); + *cursor = cursor.next_sibling(s).unwrap(); }; add_elem(s, &mut cursor, "Input", "oo"); diff --git a/tests/tests.rs b/tests/tests.rs index 02502ee..427b1b3 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -120,7 +120,7 @@ fn test_doc_ref() { let params = node_with_children(&mut s, "urllang", "Params", [eq_1, eq_2, done]); let url = node_with_children(&mut s, "urllang", "Url", [domain, params]); - let doc_ref = DocRef::new_display(&s, Location::after(&s, url), url); + let doc_ref = DocRef::new_display(&s, Location::at(&s, url), url); let actual = match ppp::pretty_print_to_string(doc_ref, 80) { Ok(actual) => actual,