Skip to content

Commit

Permalink
feat: move extra loc to front of list
Browse files Browse the repository at this point in the history
And change the commands to match, eg. Insert now inserts to the right of
the cursor instead of the left.
  • Loading branch information
justinpombrio committed May 21, 2024
1 parent 78a5f85 commit e09f6cc
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 48 deletions.
8 changes: 5 additions & 3 deletions src/engine/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions src/engine/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/pretty_doc.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -90,16 +90,16 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> {
fn lookup_style(self, style_label: StyleLabel) -> Result<Style, Self::Error> {
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,
Expand Down
7 changes: 6 additions & 1 deletion src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ impl<F: Frontend<Style = Style> + 'static> Runtime<F> {

pub fn cut(&mut self) -> Result<(), SynlessError> {
self.engine.execute(ClipboardCommand::Copy)?;
self.engine.execute(TreeEdCommand::Delete)
self.engine.execute(TreeEdCommand::Backspace)
}

/***********
Expand Down Expand Up @@ -564,6 +564,11 @@ impl<F: Frontend<Style = Style> + 'static> Runtime<F> {
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,
Expand Down
2 changes: 1 addition & 1 deletion src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
72 changes: 37 additions & 35 deletions src/tree/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<Location> {
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<Location> {
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<Location> {
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)))
}
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -171,19 +176,11 @@ impl Location {
}

pub fn prev_sibling(self, s: &Storage) -> Option<Location> {
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<Location> {
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)?;
Expand All @@ -194,6 +191,14 @@ impl Location {
}
}

pub fn next_sibling(self, s: &Storage) -> Option<Location> {
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> {
Location::before_children(s, self.parent_node(s)?)
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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));
Expand All @@ -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"),
Expand All @@ -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
}
Expand Down
16 changes: 16 additions & 0 deletions src/tree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
///
Expand Down
1 change: 0 additions & 1 deletion tests/keyhint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 0 additions & 1 deletion tests/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit e09f6cc

Please sign in to comment.