diff --git a/src/engine/command.rs b/src/engine/command.rs index 3e9cb0e..f9c8b99 100644 --- a/src/engine/command.rs +++ b/src/engine/command.rs @@ -23,7 +23,7 @@ pub enum NavCommand { #[derive(Debug)] pub enum TreeEdCommand { - /// In a listy sequence, insert the given node before the cursor. In a fixed sequence, replace + /// In a listy sequence, insert the given node after 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 at the cursor with the given node. @@ -76,10 +76,12 @@ pub enum TreeNavCommand { Last, /// Move the cursor to its parent node. Parent, + /// Move the cursor to the before the first child of the node at the cursor, if possible + /// (otherwise to the first child). + BeforeFirstChild, /// 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. + /// Move the cursor to the last child of the node at the cursor. LastChild, /// Move the cursor to the next leaf node (node with no children). NextLeaf, diff --git a/src/engine/doc.rs b/src/engine/doc.rs index 21b21e9..0481dbf 100644 --- a/src/engine/doc.rs +++ b/src/engine/doc.rs @@ -257,7 +257,7 @@ fn execute_tree_ed( match cmd { Insert(node) => match cursor.insert(s, node) { - Ok(None) => Ok(vec![(*cursor, Delete.into())]), + Ok(None) => Ok(vec![(*cursor, Backspace.into())]), Ok(Some(detached_node)) => Ok(vec![(*cursor, Insert(detached_node).into())]), Err(()) => Err(EditError::CannotPlaceNode), }, @@ -384,7 +384,10 @@ fn execute_tree_nav( PrevText => cursor.prev_text(s), NextText => cursor.next_text(s), Parent => cursor.parent(s), - FirstChild => cursor + FirstChild => cursor.node(s).and_then(|node| { + Location::at_first_child(s, node).or_else(|| Location::before_children(s, node)) + }), + BeforeFirstChild => cursor .node(s) .and_then(|node| Location::before_children(s, node)), LastChild => cursor diff --git a/src/pretty_doc.rs b/src/pretty_doc.rs index 2f81a78..a1e54c4 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, CLOSE_STYLE, CURSOR_STYLE, HOLE_STYLE, + Condition, Style, StyleLabel, ValidNotation, CURSOR_STYLE, HOLE_STYLE, OPEN_STYLE, }; use crate::tree::{Location, Node, NodeId}; use crate::util::{error, SynlessBug, SynlessError}; @@ -90,16 +90,16 @@ 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 => Style::default(), - StyleLabel::Close => { + StyleLabel::Open => { let parent = self.cursor_loc.parent_node(self.storage); let node_at_cursor = self.cursor_loc.node(self.storage); if parent == Some(self.node) && node_at_cursor.is_none() { - CLOSE_STYLE + OPEN_STYLE } else { Style::default() } } + StyleLabel::Close => Style::default(), StyleLabel::Properties { fg_color, bg_color, diff --git a/src/runtime.rs b/src/runtime.rs index d1df4e6..b2c7d86 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::Delete) + self.engine.execute(TreeEdCommand::Backspace) } /*********** @@ -564,6 +564,11 @@ impl + 'static> Runtime { register!(module, rt, TreeNavCommand::First as tree_nav_first); register!(module, rt, TreeNavCommand::Next as tree_nav_next); register!(module, rt, TreeNavCommand::Last as tree_nav_last); + register!( + module, + rt, + TreeNavCommand::BeforeFirstChild as tree_nav_before_first_child + ); register!( module, rt, diff --git a/src/style.rs b/src/style.rs index fa1e9cb..fc8bbcc 100644 --- a/src/style.rs +++ b/src/style.rs @@ -9,7 +9,7 @@ pub const HOLE_STYLE: Style = Style { ..Style::const_default() }; -pub const CLOSE_STYLE: Style = Style { +pub const OPEN_STYLE: Style = Style { cursor: Some(CursorHalf::Left), fg_color: Some((Base16Color::Base00, Priority::High)), bg_color: Some((Base16Color::Base04, Priority::High)), diff --git a/src/tree/location.rs b/src/tree/location.rs index 6641490..cb75c19 100644 --- a/src/tree/location.rs +++ b/src/tree/location.rs @@ -18,7 +18,7 @@ enum LocationInner { /// The usize is an index between chars (so it can be equal to the len) InText(Node, usize), AtNode(Node), - /// After the last child of `Node`; requires that `Node` be listy. + /// Before the first child of `Node`; requires that `Node` be listy. BelowNode(Node), } use LocationInner::{AtNode, BelowNode, InText}; @@ -41,23 +41,28 @@ impl Location { /// 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(AtNode(first_child))) - } else { - Some(Location(BelowNode(node))) + match node.arity(s) { + Arity::Texty => None, + Arity::Fixed(_) => node.first_child(s).map(|child| Location(AtNode(child))), + Arity::Listy(_) => Some(Location(BelowNode(node))), } } + /// Returns the location at the node's first child, if any. + pub fn at_first_child(s: &Storage, node: Node) -> Option { + node.first_child(s).map(|child| Location(AtNode(child))) + } + /// 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 { - 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 !node.can_have_children(s) { + return None; + } + if let Some(last_child) = node.last_child(s) { + Some(Location(AtNode(last_child))) + } else { + Some(Location(BelowNode(node))) } } @@ -124,8 +129,8 @@ impl Location { let mut path_to_root = Vec::new(); let (mut node, target) = match self.0 { BelowNode(node) => { - if let Some(last_child) = node.last_child(s) { - (last_child, ppp::FocusTarget::End) + if let Some(first_child) = node.first_child(s) { + (first_child, ppp::FocusTarget::Start) } else { // NOTE: This relies on the node's notation containing a `Notation::FocusMark`. (node, ppp::FocusTarget::Mark) @@ -171,19 +176,11 @@ impl Location { } pub fn prev_sibling(self, s: &Storage) -> Option { - match self.0 { - 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 { let node = match self.0 { AtNode(node) => node, InText(_, _) | BelowNode(_) => return None, }; - if let Some(sibling) = node.next_sibling(s) { + if let Some(sibling) = node.prev_sibling(s) { return Some(Location(AtNode(sibling))); } let parent = node.parent(s)?; @@ -194,6 +191,14 @@ impl Location { } } + pub fn next_sibling(self, s: &Storage) -> Option { + match self.0 { + AtNode(node) => Some(Location(AtNode(node.next_sibling(s)?))), + BelowNode(parent) => parent.first_child(s).map(|child| Location(AtNode(child))), + InText(_, _) => None, + } + } + /// 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)?) @@ -294,7 +299,7 @@ impl Location { * Mutation * ************/ - /// In a listy sequence, inserts `new_node` to the left of this location and returns + /// In a listy sequence, inserts `new_node` to the right 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. /// @@ -322,8 +327,8 @@ impl Location { Arity::Listy(_) => { let success = match self.0 { InText(_, _) => bug!("insert: bug in textiness check"), - AtNode(node) => node.insert_before(s, new_node), - BelowNode(_) => parent.insert_last_child(s, new_node), + AtNode(node) => node.insert_after(s, new_node), + BelowNode(_) => parent.insert_first_child(s, new_node), }; if success { *self = Location(AtNode(new_node)); @@ -340,10 +345,7 @@ impl Location { /// executed from. #[must_use] 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 node = self.node(s)?; let parent = node.parent(s)?; match parent.arity(s) { Arity::Texty => bug!("texty parent"), @@ -357,15 +359,15 @@ impl Location { } } Arity::Listy(_) => { - let next_loc = self.next_sibling(s).bug(); - let opt_prev_loc = self.prev_sibling(s); + let prev_loc = self.prev_sibling(s).bug(); + let opt_next_loc = self.next_sibling(s); if node.detach(s) { *self = if move_left { - opt_prev_loc.unwrap_or(next_loc) + prev_loc } else { - next_loc + opt_next_loc.unwrap_or(prev_loc) }; - Some((node, next_loc)) + Some((node, prev_loc)) } else { None } diff --git a/src/tree/node.rs b/src/tree/node.rs index e308a0d..943f749 100644 --- a/src/tree/node.rs +++ b/src/tree/node.rs @@ -423,6 +423,22 @@ impl Node { } } + /// Attempts to insert `new_child` as the first child of `self`. + /// Returns false and does nothing if any of: + /// + /// - `self` is not listy. + /// - The `new_child` is incompatible with the arity of `self`. + /// - The `new_child` is not a root. + /// - The `new_child` is the root of `self`. + #[must_use] + pub fn insert_first_child(self, s: &mut Storage, new_child: Node) -> bool { + if self.is_listy_and_accepts_child(s, new_child) { + s.forest_mut().insert_first_child(self.0, new_child.0) + } else { + false + } + } + /// Attempts to insert `new_child` as the last child of `self`. /// Returns false and does nothing if any of: /// diff --git a/tests/keyhint.rs b/tests/keyhint.rs index 5e417e7..8505b03 100644 --- a/tests/keyhint.rs +++ b/tests/keyhint.rs @@ -33,7 +33,6 @@ 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 6ee6457..07341f7 100644 --- a/tests/selection.rs +++ b/tests/selection.rs @@ -28,7 +28,6 @@ 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");