1
0
mirror of https://github.com/chylex/Bark-Browser.git synced 2025-09-15 14:32:12 +02:00

Compare commits

10 Commits

60 changed files with 4468 additions and 559 deletions

1
Cargo.lock generated
View File

@@ -311,7 +311,6 @@ dependencies = [
[[package]] [[package]]
name = "slab_tree" name = "slab_tree"
version = "0.3.2" version = "0.3.2"
source = "git+https://github.com/jsinger67/slab-tree.git?branch=set_root_fix#75a8dfa3b1f96f76ebe09e2e988deeffe6db54b6"
dependencies = [ dependencies = [
"snowflake", "snowflake",
] ]

View File

@@ -21,5 +21,4 @@ slab_tree = "0.3.2"
users = "0.11" users = "0.11"
[patch.crates-io.slab_tree] [patch.crates-io.slab_tree]
git = "https://github.com/jsinger67/slab-tree.git" path = "./lib/slab-tree"
branch = "set_root_fix"

View File

@@ -42,3 +42,9 @@ Run `docker build --output out .` to build a release binary into the `out/` fold
This project exists 1) because I couldn't find any tree-based file manager I liked and 2) because I wanted to have fun writing Rust, and I don't really want to spend time reading and reviewing pull requests. This project exists 1) because I couldn't find any tree-based file manager I liked and 2) because I wanted to have fun writing Rust, and I don't really want to spend time reading and reviewing pull requests.
For now, issues are closed, and I'm not accepting any major contributions — especially ones related to the roadmap. If you have a small idea, issue, or pull request, feel free to start a [discussion](https://github.com/chylex/Bark-Browser/discussions). For now, issues are closed, and I'm not accepting any major contributions — especially ones related to the roadmap. If you have a small idea, issue, or pull request, feel free to start a [discussion](https://github.com/chylex/Bark-Browser/discussions).
# Dependencies
For a full list of dependencies, see [Cargo.toml](Cargo.toml).
This repository includes the sources of [slab-tree](https://github.com/iwburns/slab-tree) (by [iwburns](https://github.com/iwburns)) with a [bug fix for `set_root`](https://github.com/iwburns/slab-tree/pull/28) (by [jsinger67](https://github.com/jsinger67)) and additional modifications from me.

2
lib/slab-tree/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/.idea/
/target/

16
lib/slab-tree/Cargo.lock generated Normal file
View File

@@ -0,0 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "slab_tree"
version = "0.3.2"
dependencies = [
"snowflake",
]
[[package]]
name = "snowflake"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27207bb65232eda1f588cf46db2fee75c0808d557f6b3cf19a75f5d6d7c94df1"

15
lib/slab-tree/Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "slab_tree"
version = "0.3.2"
authors = ["Ian <iwburns8@gmail.com>"]
description = "A vec-backed tree structure with tree-specific generational indexes."
documentation = "https://docs.rs/slab_tree"
repository = "https://github.com/iwburns/slab-tree"
readme = "README.md"
keywords = ["tree", "slab", "slab-tree"]
categories = ["data-structures"]
license = "MIT"
edition = "2018"
[dependencies]
snowflake = "1.3.0"

69
lib/slab-tree/README.md Normal file
View File

@@ -0,0 +1,69 @@
# slab_tree
[![Build Status](https://travis-ci.org/iwburns/slab-tree.svg?branch=master)](https://travis-ci.org/iwburns/slab-tree)
[![](https://tokei.rs/b1/github/iwburns/slab-tree)](https://github.com/iwburns/slab-tree)
A vec-backed tree structure with tree-specific generational indexes.
## Overview
This library provides a `Tree<T>` struct which allows the creation and manipulation of in-memory trees.
The tree itself is backed by a vector and the tree's node relationships are managed by tree-specific
generational indexes called `NodeId`s (more below). In addition, "views" of tree nodes are handed out
which are either immutable (`NodeRef`) or mutable (`NodeMut`) instead of handing out references
directly. Most tree mutations are achieved by modifying `NodeMut`s instead of talking to the tree
itself.
`Tree`s in this crate are "just" trees. They do not allow cycles, and they do not allow arbitrary
graph structures to be created. Each node in the tree can have an arbitrary number of children, and
there is no weight associated with edges between the nodes in the tree.
**Please Note:** this crate does not support comparison-based data insertion. In other words, this is
not a binary search tree (or any other kind of search tree) crate. It is purely a crate for storing
data in a hierarchical manner. The caller must know the structure that they wish to build and then use
this crate to do so; this library will not make those structural decisions for you.
## Safety
This crate uses `#![forbid(unsafe_code)]` to prevent any and all `unsafe` code usage.
## Example Usage
```rust
use slab_tree::*;
fn main() {
// "hello"
// / \
// "world" "trees"
// |
// "are"
// |
// "cool"
let mut tree = TreeBuilder::new().with_root("hello").build();
let root_id = tree.root_id().expect("root doesn't exist?");
let mut hello = tree.get_mut(root_id).unwrap();
hello.append("world");
hello
.append("trees")
.append("are")
.append("cool");
}
```
## `NodeId`s
`NodeId`s are tree-specific generational indexes. Using generational indexes means that we can re-use
space inside the tree (after nodes have been removed) without also having to re-use the same tree
indexes which could potentially cause confusion and bugs. The "tree-specific" part means that indexes
from one tree cannot be confused for indexes for another tree. This is because each index contains a
process-unique-id which is shared by the tree from which that index originated.
## Project Goals
* Allow caller control of as many allocations as possible (through pre-allocation)
* Fast and Ergonomic Node insertion and removal
* Arbitrary Tree structure creation and manipulation
## Non-Goals
* Arbitrary _Graph_ structure creation and manipulation
* Comparison-based node insertion of any kind

View File

@@ -0,0 +1,22 @@
///
/// Describes all the possible ways to remove a Node from a Tree.
///
pub enum RemoveBehavior {
///
/// All children of the removed Node will be dropped from the Tree. All children (and all
/// Nodes in each of their sub-trees) will no longer exist in the Tree after this operation.
///
/// This is slower than `OrphanChildren` but frees up space inside the Tree.
///
DropChildren,
///
/// All children of the removed Node will be left in the Tree (still accessible via NodeIds).
/// However, each child (and their sub-trees) will no longer be connected to the rest of the
/// Nodes in the Tree.
///
/// Orphaned nodes will live in the Tree until they are manually removed or until the Tree is
/// Dropped. This is faster than `DropChildren` but doesn't free up any space inside the Tree.
///
OrphanChildren,
}

View File

@@ -0,0 +1,136 @@
use crate::node::Node;
use crate::slab;
use crate::NodeId;
use snowflake::ProcessUniqueId;
///
/// A wrapper around a Slab containing Node<T> values.
///
/// Groups a collection of Node<T>s with a process unique id.
///
#[derive(Debug, PartialEq)]
pub(crate) struct CoreTree<T> {
id: ProcessUniqueId,
slab: slab::Slab<Node<T>>,
}
impl<T> CoreTree<T> {
pub(crate) fn new(capacity: usize) -> CoreTree<T> {
CoreTree {
id: ProcessUniqueId::new(),
slab: slab::Slab::new(capacity),
}
}
pub(crate) fn capacity(&self) -> usize {
self.slab.capacity()
}
pub(crate) fn insert(&mut self, data: T) -> NodeId {
let key = self.slab.insert(Node::new(data));
self.new_node_id(key)
}
pub(crate) fn remove(&mut self, node_id: NodeId) -> Option<T> {
self.filter_by_tree_id(node_id)
.and_then(|id| self.slab.remove(id.index))
.map(|node| node.data)
}
pub(crate) fn get(&self, node_id: NodeId) -> Option<&Node<T>> {
self.filter_by_tree_id(node_id)
.and_then(|id| self.slab.get(id.index))
}
pub(crate) fn get_mut(&mut self, node_id: NodeId) -> Option<&mut Node<T>> {
self.filter_by_tree_id(node_id)
.and_then(move |id| self.slab.get_mut(id.index))
}
fn new_node_id(&self, index: slab::Index) -> NodeId {
NodeId {
tree_id: self.id,
index,
}
}
fn filter_by_tree_id(&self, node_id: NodeId) -> Option<NodeId> {
if node_id.tree_id != self.id {
return None;
}
Some(node_id)
}
}
#[cfg_attr(tarpaulin, skip)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capacity() {
let capacity = 5;
let tree = CoreTree::<i32>::new(capacity);
assert_eq!(tree.capacity(), capacity);
}
#[test]
fn insert() {
let mut tree = CoreTree::new(0);
let id = tree.insert(1);
let id2 = tree.insert(3);
assert_eq!(tree.get(id).unwrap().data, 1);
assert_eq!(tree.get(id2).unwrap().data, 3);
}
#[test]
fn remove() {
let mut tree = CoreTree::new(0);
let id = tree.insert(1);
assert_eq!(tree.get(id).unwrap().data, 1);
let one = tree.remove(id);
assert!(one.is_some());
let one = one.unwrap();
assert_eq!(one, 1);
}
#[test]
fn get() {
let mut tree = CoreTree::new(0);
let id = tree.insert(1);
let id2 = tree.insert(3);
assert_eq!(tree.get(id).unwrap().data, 1);
assert_eq!(tree.get(id2).unwrap().data, 3);
}
#[test]
fn get_mut() {
let mut tree = CoreTree::new(0);
let id = tree.insert(1);
let id2 = tree.insert(3);
assert_eq!(tree.get_mut(id).unwrap().data, 1);
assert_eq!(tree.get_mut(id2).unwrap().data, 3);
}
#[test]
fn get_with_bad_id() {
let mut tree = CoreTree::new(0);
let tree2: CoreTree<i32> = CoreTree::new(0);
let mut id = tree.insert(1);
id.tree_id = tree2.id; // oops, wrong tree id.
let result = tree.get(id);
assert!(result.is_none());
}
}

223
lib/slab-tree/src/iter.rs Normal file
View File

@@ -0,0 +1,223 @@
use crate::node::*;
use crate::tree::Tree;
use crate::NodeId;
// todo: document this
pub struct Ancestors<'a, T> {
node_id: Option<NodeId>,
tree: &'a Tree<T>,
}
impl<'a, T> Ancestors<'a, T> {
pub(crate) fn new(node_id: Option<NodeId>, tree: &'a Tree<T>) -> Ancestors<T> {
Ancestors { node_id, tree }
}
}
impl<'a, T> Iterator for Ancestors<'a, T> {
type Item = NodeRef<'a, T>;
fn next(&mut self) -> Option<NodeRef<'a, T>> {
self.node_id
.take()
.and_then(|node_id| self.tree.get_node_relatives(node_id).parent)
.map(|id| {
self.node_id = Some(id);
NodeRef::new(id, self.tree)
})
}
}
// possibly re-name this, not sure how I feel about it
pub struct NextSiblings<'a, T> {
node_id: Option<NodeId>,
tree: &'a Tree<T>,
}
impl<'a, T> NextSiblings<'a, T> {
pub(crate) fn new(node_id: Option<NodeId>, tree: &'a Tree<T>) -> NextSiblings<T> {
NextSiblings { node_id, tree }
}
}
impl<'a, T> Iterator for NextSiblings<'a, T> {
type Item = NodeRef<'a, T>;
fn next(&mut self) -> Option<NodeRef<'a, T>> {
self.node_id.take().map(|node_id| {
self.node_id = self.tree.get_node_relatives(node_id).next_sibling;
NodeRef::new(node_id, self.tree)
})
}
}
/// Depth-first pre-order iterator
pub struct PreOrder<'a, T> {
start: Option<NodeRef<'a, T>>,
children: Vec<NextSiblings<'a, T>>,
tree: &'a Tree<T>,
}
impl<'a, T> PreOrder<'a, T> {
pub(crate) fn new(node: &NodeRef<'a, T>, tree: &'a Tree<T>) -> PreOrder<'a, T> {
let children = vec![];
let start = tree.get(node.node_id());
PreOrder {
start,
children,
tree,
}
}
}
impl<'a, T> Iterator for PreOrder<'a, T> {
type Item = NodeRef<'a, T>;
fn next(&mut self) -> Option<NodeRef<'a, T>> {
if let Some(node) = self.start.take() {
let first_child_id = node.first_child().map(|child_ref| child_ref.node_id());
self.children
.push(NextSiblings::new(first_child_id, self.tree));
Some(node)
} else {
while !self.children.is_empty() {
if let Some(node_ref) = self.children.last_mut().and_then(Iterator::next) {
if let Some(first_child) = node_ref.first_child() {
self.children
.push(NextSiblings::new(Some(first_child.node_id()), self.tree));
}
return Some(node_ref);
}
self.children.pop();
}
None
}
}
}
/// Depth-first post-order iterator
pub struct PostOrder<'a, T> {
nodes: Vec<(NodeRef<'a, T>, NextSiblings<'a, T>)>,
tree: &'a Tree<T>,
}
impl<'a, T> PostOrder<'a, T> {
pub(crate) fn new(node: &NodeRef<'a, T>, tree: &'a Tree<T>) -> PostOrder<'a, T> {
let node = tree
.get(node.node_id())
.expect("getting node of node ref id");
let first_child_id = node.first_child().map(|first_child| first_child.node_id());
let nodes = vec![(node, NextSiblings::new(first_child_id, tree))];
PostOrder { nodes, tree }
}
}
impl<'a, T> Iterator for PostOrder<'a, T> {
type Item = NodeRef<'a, T>;
fn next(&mut self) -> Option<NodeRef<'a, T>> {
if let Some((node, mut children)) = self.nodes.pop() {
if let Some(next) = children.next() {
self.nodes.push((node, children));
let mut node_id = next.node_id();
loop {
let node = self.tree.get(node_id).expect("getting node of node ref id");
if let Some(first_child) = node.first_child() {
node_id = first_child.node_id();
let mut children = NextSiblings::new(Some(node_id), self.tree);
assert!(children.next().is_some(), "skipping first child");
self.nodes.push((node, children));
} else {
break Some(node);
}
}
} else {
Some(node)
}
} else {
None
}
}
}
/// Depth-first level-order iterator
pub struct LevelOrder<'a, T> {
start: NodeRef<'a, T>,
levels: Vec<(NodeId, NextSiblings<'a, T>)>,
tree: &'a Tree<T>,
}
impl<'a, T> LevelOrder<'a, T> {
pub(crate) fn new(node: &NodeRef<'a, T>, tree: &'a Tree<T>) -> LevelOrder<'a, T> {
let start = tree
.get(node.node_id())
.expect("getting node of node ref id");
let levels = Vec::new();
LevelOrder {
start,
levels,
tree,
}
}
}
impl<'a, T> Iterator for LevelOrder<'a, T> {
type Item = NodeRef<'a, T>;
fn next(&mut self) -> Option<NodeRef<'a, T>> {
if self.levels.is_empty() {
let first_child_id = self.start.first_child().map(|child| child.node_id());
self.levels.push((
self.start.node_id(),
NextSiblings::new(first_child_id, self.tree),
));
let node = self
.tree
.get(self.start.node_id())
.expect("getting node of existing node ref id");
Some(node)
} else {
let mut on_level = self.levels.len();
let next_level = on_level + 1;
let mut level = on_level;
while level > 0 {
if let Some(node) = self.levels.last_mut().expect("non-empty levels").1.next() {
if level >= on_level {
return Some(node);
} else {
let first_child_id = node.first_child().map(|child| child.node_id());
self.levels
.push((node.node_id(), NextSiblings::new(first_child_id, self.tree)));
level += 1;
}
} else {
let (node_id, _) = self.levels.pop().expect("on level > 0");
if let Some(next) = self.levels.last_mut().and_then(|level| level.1.next()) {
let first_child_id = next.first_child().map(|child| child.node_id());
self.levels
.push((next.node_id(), NextSiblings::new(first_child_id, self.tree)));
} else if level == 1 {
if on_level < next_level {
on_level += 1;
let node = self
.tree
.get(node_id)
.expect("getting node of existing node ref id");
let first_child_id = node.first_child().map(|child| child.node_id());
self.levels.push((
node.node_id(),
NextSiblings::new(first_child_id, self.tree),
));
} else {
break;
}
} else {
level -= 1;
}
}
}
None
}
}
}

96
lib/slab-tree/src/lib.rs Normal file
View File

@@ -0,0 +1,96 @@
#![forbid(unsafe_code)]
//!
//! # slab_tree
//!
//! [![Build Status](https://travis-ci.org/iwburns/slab-tree.svg?branch=master)](https://travis-ci.org/iwburns/slab-tree)
//! [![](https://tokei.rs/b1/github/iwburns/slab-tree)](https://github.com/iwburns/slab-tree)
//!
//! A vec-backed tree structure with tree-specific generational indexes.
//!
//! ## Overview
//!
//! This library provides a `Tree<T>` struct which allows the creation and manipulation of in-memory trees.
//! The tree itself is backed by a vector and the tree's node relationships are managed by tree-specific
//! generational indexes called `NodeId`s (more below). In addition, "views" of tree nodes are handed out
//! which are either immutable (`NodeRef`) or mutable (`NodeMut`) instead of handing out references
//! directly. Most tree mutations are achieved by modifying `NodeMut`s instead of talking to the tree
//! itself.
//!
//! `Tree`s in this crate are "just" trees. They do not allow cycles, and they do not allow arbitrary
//! graph structures to be created. Each node in the tree can have an arbitrary number of children, and
//! there is no weight associated with edges between the nodes in the tree.
//!
//! **Please Note:** this crate does not support comparison-based data insertion. In other words, this is
//! not a binary search tree (or any other kind of search tree) crate. It is purely a crate for storing
//! data in a hierarchical manner. The caller must know the structure that they wish to build and then use
//! this crate to do so; this library will not make those structural decisions for you.
//!
//! ## Safety
//! This crate uses `#![forbid(unsafe_code)]` to prevent any and all `unsafe` code usage.
//!
//! ## Example Usage
//! ```
//! use slab_tree::*;
//!
//! // "hello"
//! // / \
//! // "world" "trees"
//! // |
//! // "are"
//! // |
//! // "cool"
//!
//! let mut tree = TreeBuilder::new().with_root("hello").build();
//! let root_id = tree.root_id().expect("root doesn't exist?");
//! let mut hello = tree.get_mut(root_id).unwrap();
//!
//! hello.append("world");
//! hello
//! .append("trees")
//! .append("are")
//! .append("cool");
//! ```
//!
//! ## `NodeId`s
//! `NodeId`s are tree-specific generational indexes. Using generational indexes means that we can re-use
//! space inside the tree (after nodes have been removed) without also having to re-use the same tree
//! indexes which could potentially cause confusion and bugs. The "tree-specific" part means that indexes
//! from one tree cannot be confused for indexes for another tree. This is because each index contains a
//! process-unique-id which is shared by the tree from which that index originated.
//!
//! ## Project Goals
//! * Allow caller control of as many allocations as possible (through pre-allocation)
//! * Fast and Ergonomic Node insertion and removal
//! * Arbitrary Tree structure creation and manipulation
//!
//! ## Non-Goals
//! * Arbitrary _Graph_ structure creation and manipulation
//! * Comparison-based node insertion of any kind
//!
pub mod behaviors;
mod core_tree;
pub mod iter;
pub mod node;
mod slab;
pub mod tree;
pub use crate::behaviors::RemoveBehavior;
pub use crate::iter::Ancestors;
pub use crate::iter::NextSiblings;
pub use crate::node::NodeMut;
pub use crate::node::NodeRef;
pub use crate::tree::Tree;
pub use crate::tree::TreeBuilder;
use snowflake::ProcessUniqueId;
///
/// An identifier used to differentiate between Nodes and tie
/// them to a specific tree.
///
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub struct NodeId {
tree_id: ProcessUniqueId,
index: slab::Index,
}

39
lib/slab-tree/src/node.rs Normal file
View File

@@ -0,0 +1,39 @@
mod bark_extensions;
mod node_mut;
mod node_ref;
pub use self::bark_extensions::*;
pub use self::node_mut::NodeMut;
pub use self::node_ref::NodeRef;
use crate::NodeId;
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct Relatives {
pub(crate) parent: Option<NodeId>,
pub(crate) prev_sibling: Option<NodeId>,
pub(crate) next_sibling: Option<NodeId>,
pub(crate) first_child: Option<NodeId>,
pub(crate) last_child: Option<NodeId>,
}
#[derive(Debug, PartialEq)]
pub(crate) struct Node<T> {
pub(crate) data: T,
pub(crate) relatives: Relatives,
}
impl<T> Node<T> {
pub(crate) fn new(data: T) -> Node<T> {
Node {
data,
relatives: Relatives {
parent: None,
prev_sibling: None,
next_sibling: None,
first_child: None,
last_child: None,
},
}
}
}

View File

@@ -0,0 +1,79 @@
use crate::NodeId;
use crate::NodeMut;
use crate::NodeRef;
impl<'a, T> NodeRef<'a, T> {
pub fn parent_id(&self) -> Option<NodeId> {
self.parent().map(|node| node.node_id())
}
pub fn first_child_id(&self) -> Option<NodeId> {
self.first_child().map(|node| node.node_id())
}
pub fn last_child_id(&self) -> Option<NodeId> {
self.last_child().map(|node| node.node_id())
}
pub fn prev_sibling_id(&self) -> Option<NodeId> {
self.prev_sibling().map(|node| node.node_id())
}
pub fn next_sibling_id(&self) -> Option<NodeId> {
self.next_sibling().map(|node| node.node_id())
}
pub fn last_descendant_or_self(&self) -> NodeRef<'a, T> {
let mut node = NodeRef::new(self.node_id(), self.tree);
while let Some(id) = node.get_self_as_node().relatives.last_child {
node = NodeRef::new(id, self.tree);
}
node
}
pub fn above(&self) -> Option<NodeRef<T>> {
if let Some(prev_sibling) = self.prev_sibling() {
Some(prev_sibling.last_descendant_or_self())
} else {
self.parent()
}
}
pub fn above_id(&self) -> Option<NodeId> {
self.above().map(|node| node.node_id())
}
pub fn below(&self) -> Option<NodeRef<T>> {
self.first_child()
.or_else(|| self.next_sibling())
.or_else(|| self.ancestors().find_map(|ancestor| ancestor.next_sibling_id()).map(|id| NodeRef::new(id, self.tree)))
}
pub fn below_id(&self) -> Option<NodeId> {
self.below().map(|node| node.node_id())
}
}
impl<'a, T> NodeMut<'a, T> {
pub fn parent_id(&mut self) -> Option<NodeId> {
self.parent().map(|node| node.node_id())
}
pub fn first_child_id(&mut self) -> Option<NodeId> {
self.first_child().map(|node| node.node_id())
}
pub fn last_child_id(&mut self) -> Option<NodeId> {
self.last_child().map(|node| node.node_id())
}
pub fn prev_sibling_id(&mut self) -> Option<NodeId> {
self.prev_sibling().map(|node| node.node_id())
}
pub fn next_sibling_id(&mut self) -> Option<NodeId> {
self.next_sibling().map(|node| node.node_id())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
use crate::iter::Ancestors;
use crate::iter::LevelOrder;
use crate::iter::NextSiblings;
use crate::iter::PostOrder;
use crate::iter::PreOrder;
use crate::node::Node;
use crate::tree::Tree;
use crate::NodeId;
///
/// An immutable reference to a given `Node`'s data and its relatives.
///
pub struct NodeRef<'a, T> {
pub(super) node_id: NodeId,
pub(super) tree: &'a Tree<T>,
}
impl<'a, T> NodeRef<'a, T> {
pub(crate) fn new(node_id: NodeId, tree: &'a Tree<T>) -> NodeRef<T> {
NodeRef { node_id, tree }
}
///
/// Returns the `NodeId` that identifies this `Node` in the tree.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
/// let root_id = tree.root_id().expect("root doesn't exist?");
/// let root = tree.root_mut().expect("root doesn't exist?");
///
/// let root_id_again = root.as_ref().node_id();
///
/// assert_eq!(root_id_again, root_id);
/// ```
///
pub fn node_id(&self) -> NodeId {
self.node_id
}
///
/// Returns a reference to the data contained by the given `Node`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert_eq!(root.data(), &1);
/// ```
///
pub fn data(&self) -> &'a T {
if let Some(node) = self.tree.get_node(self.node_id) {
&node.data
} else {
unreachable!()
}
}
///
/// Returns a `NodeRef` pointing to this `Node`'s parent. Returns a `Some`-value containing
/// the `NodeRef` if this `Node` has a parent; otherwise returns a `None`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert!(root.parent().is_none());
/// ```
///
pub fn parent(&self) -> Option<NodeRef<T>> {
self.get_self_as_node()
.relatives
.parent
.map(|id| NodeRef::new(id, self.tree))
}
///
/// Returns a `NodeRef` pointing to this `Node`'s previous sibling. Returns a `Some`-value
/// containing the `NodeRef` if this `Node` has a previous sibling; otherwise returns a `None`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert!(root.prev_sibling().is_none());
/// ```
///
pub fn prev_sibling(&self) -> Option<NodeRef<T>> {
self.get_self_as_node()
.relatives
.prev_sibling
.map(|id| NodeRef::new(id, self.tree))
}
///
/// Returns a `NodeRef` pointing to this `Node`'s next sibling. Returns a `Some`-value
/// containing the `NodeRef` if this `Node` has a next sibling; otherwise returns a `None`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert!(root.next_sibling().is_none());
/// ```
///
pub fn next_sibling(&self) -> Option<NodeRef<T>> {
self.get_self_as_node()
.relatives
.next_sibling
.map(|id| NodeRef::new(id, self.tree))
}
///
/// Returns a `NodeRef` pointing to this `Node`'s first child. Returns a `Some`-value
/// containing the `NodeRef` if this `Node` has a first child; otherwise returns a `None`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert!(root.first_child().is_none());
/// ```
///
pub fn first_child(&self) -> Option<NodeRef<T>> {
self.get_self_as_node()
.relatives
.first_child
.map(|id| NodeRef::new(id, self.tree))
}
///
/// Returns a `NodeRef` pointing to this `Node`'s last child. Returns a `Some`-value
/// containing the `NodeRef` if this `Node` has a last child; otherwise returns a `None`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert!(root.last_child().is_none());
/// ```
///
pub fn last_child(&self) -> Option<NodeRef<T>> {
self.get_self_as_node()
.relatives
.last_child
.map(|id| NodeRef::new(id, self.tree))
}
///
/// Returns a `Iterator` over the given `Node`'s ancestors. Each call to `Iterator::next()`
/// returns a `NodeRef` pointing to the current `Node`'s parent.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let leaf_id = tree.root_mut().expect("root doesn't exist?")
/// .append(2)
/// .append(3)
/// .append(4)
/// .node_id();
///
/// let leaf = tree.get(leaf_id).unwrap();
///
/// let values = [3, 2, 1];
/// for (i, ancestor) in leaf.ancestors().enumerate() {
/// assert_eq!(ancestor.data(), &values[i]);
/// }
/// ```
///
pub fn ancestors(&self) -> Ancestors<'a, T> {
Ancestors::new(Some(self.node_id), self.tree)
}
///
/// Returns a `Iterator` over the given `Node`'s children. Each call to `Iterator::next()`
/// returns a `NodeRef` pointing to the next child of the given `Node`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
///
/// let mut root = tree.root_mut().expect("root doesn't exist?");
/// root.append(2);
/// root.append(3);
/// root.append(4);
///
/// let root = root.as_ref();
///
/// let values = [2, 3, 4];
/// for (i, child) in root.children().enumerate() {
/// assert_eq!(child.data(), &values[i]);
/// }
/// ```
///
pub fn children(&self) -> NextSiblings<'a, T> {
let first_child_id = self.tree.get_node_relatives(self.node_id).first_child;
NextSiblings::new(first_child_id, self.tree)
}
/// Depth-first pre-order traversal.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(0i64).build();
/// let root_id = tree.root().unwrap().node_id();
/// let one_id = tree.get_mut(root_id).unwrap().append(1).node_id();
/// tree.get_mut(one_id).unwrap().append(2);
/// tree.get_mut(one_id).unwrap().append(3);
/// tree.get_mut(root_id).unwrap().append(4);
/// let pre_order = tree.root().unwrap().traverse_pre_order()
/// .map(|node_ref| node_ref.data().clone()).collect::<Vec<i64>>();
/// assert_eq!(pre_order, vec![0, 1, 2, 3, 4]);
/// ```
pub fn traverse_pre_order(&self) -> PreOrder<'a, T> {
PreOrder::new(self, self.tree)
}
/// Depth-first post-order traversal.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(0i64).build();
/// let root_id = tree.root().unwrap().node_id();
/// let one_id = tree.get_mut(root_id).unwrap().append(1).node_id();
/// tree.get_mut(one_id).unwrap().append(2);
/// tree.get_mut(one_id).unwrap().append(3);
/// tree.get_mut(root_id).unwrap().append(4);
/// let post_order = tree.root().unwrap().traverse_post_order()
/// .map(|node_ref| node_ref.data().clone()).collect::<Vec<i64>>();
/// assert_eq!(post_order, vec![2, 3, 1, 4, 0]);
/// ```
pub fn traverse_post_order(&self) -> PostOrder<'a, T> {
PostOrder::new(self, self.tree)
}
/// Depth-first level-order traversal.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(0i64).build();
/// let root_id = tree.root().unwrap().node_id();
/// let one_id = tree.get_mut(root_id).unwrap().append(1).node_id();
/// tree.get_mut(one_id).unwrap().append(2);
/// tree.get_mut(one_id).unwrap().append(3);
/// tree.get_mut(root_id).unwrap().append(4);
/// let level_order = tree.root().unwrap().traverse_level_order()
/// .map(|node_ref| node_ref.data().clone()).collect::<Vec<i64>>();
/// assert_eq!(level_order, vec![0, 1, 4, 2, 3]);
/// ```
pub fn traverse_level_order(&self) -> LevelOrder<'a, T> {
LevelOrder::new(self, self.tree)
}
pub(super) fn get_self_as_node(&self) -> &Node<T> {
if let Some(node) = self.tree.get_node(self.node_id) {
&node
} else {
unreachable!()
}
}
}
#[cfg_attr(tarpaulin, skip)]
#[cfg(test)]
mod node_ref_tests {
use crate::tree::Tree;
#[test]
fn data() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert_eq!(root_ref.data(), &1);
}
#[test]
fn parent() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert!(root_ref.parent().is_none());
}
#[test]
fn prev_sibling() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert!(root_ref.prev_sibling().is_none());
}
#[test]
fn next_sibling() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert!(root_ref.next_sibling().is_none());
}
#[test]
fn first_child() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert!(root_ref.first_child().is_none());
}
#[test]
fn last_child() {
let mut tree = Tree::new();
tree.set_root(1);
let root_id = tree.root_id().expect("root doesn't exist?");
let root_ref = tree.get(root_id).unwrap();
assert!(root_ref.last_child().is_none());
}
#[test]
fn ancestors() {
let mut tree = Tree::new();
tree.set_root(1);
let mut root_mut = tree.root_mut().expect("root doesn't exist");
let node_id = root_mut.append(2).append(3).append(4).append(5).node_id();
let values = [4, 3, 2, 1];
let bottom_node = tree.get(node_id).unwrap();
for (i, node_ref) in bottom_node.ancestors().enumerate() {
assert_eq!(node_ref.data(), &values[i]);
}
}
#[test]
fn children() {
let mut tree = Tree::new();
tree.set_root(1);
let mut root = tree.root_mut().expect("root doesn't exist");
root.append(2);
root.append(3);
root.append(4);
root.append(5);
let values = [2, 3, 4, 5];
let root = root.as_ref();
for (i, node_ref) in root.children().enumerate() {
assert_eq!(node_ref.data(), &values[i]);
}
}
}

497
lib/slab-tree/src/slab.rs Normal file
View File

@@ -0,0 +1,497 @@
use std::mem;
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub(super) struct Index {
index: usize,
generation: u64,
}
#[derive(Debug, PartialEq)]
enum Slot<T> {
Empty { next_free_slot: Option<usize> },
Filled { item: T, generation: u64 },
}
#[derive(Debug, PartialEq)]
pub(super) struct Slab<T> {
data: Vec<Slot<T>>,
first_free_slot: Option<usize>,
generation: u64,
}
impl<T> Slab<T> {
pub(super) fn new(capacity: usize) -> Slab<T> {
Slab {
data: Vec::with_capacity(capacity),
first_free_slot: None,
generation: 0,
}
}
pub(super) fn capacity(&self) -> usize {
self.data.capacity()
}
pub(super) fn insert(&mut self, item: T) -> Index {
let new_slot = Slot::Filled {
item,
generation: self.generation,
};
if let Some(index) = self.first_free_slot {
match mem::replace(&mut self.data[index], new_slot) {
Slot::Empty { next_free_slot } => {
self.first_free_slot = next_free_slot;
}
_ => unreachable!(),
};
Index {
index,
generation: self.generation,
}
} else {
self.data.push(new_slot);
Index {
index: self.data.len() - 1,
generation: self.generation,
}
}
}
pub(super) fn remove(&mut self, index: Index) -> Option<T> {
if index.index >= self.data.len() {
return None;
}
let slot = mem::replace(
&mut self.data[index.index],
Slot::Empty {
next_free_slot: self.first_free_slot,
},
);
match slot {
Slot::Filled { item, generation } => {
if index.generation == generation {
self.generation += 1;
self.first_free_slot = Some(index.index);
Some(item)
} else {
self.data[index.index] = Slot::Filled { item, generation };
None
}
}
s => {
self.data[index.index] = s;
None
}
}
}
pub(super) fn get(&self, index: Index) -> Option<&T> {
self.data.get(index.index).and_then(|slot| match slot {
Slot::Filled { item, generation } => {
if index.generation == *generation {
return Some(item);
}
None
}
_ => None,
})
}
pub(super) fn get_mut(&mut self, index: Index) -> Option<&mut T> {
self.data.get_mut(index.index).and_then(|slot| match slot {
Slot::Filled { item, generation } => {
if index.generation == *generation {
return Some(item);
}
None
}
_ => None,
})
}
}
#[cfg_attr(tarpaulin, skip)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capacity() {
let capacity = 5;
let slab = Slab::<i32>::new(capacity);
assert_eq!(slab.capacity(), capacity);
assert!(slab.first_free_slot.is_none());
assert_eq!(slab.generation, 0);
}
#[test]
fn insert() {
let capacity = 2;
let mut slab = Slab::new(capacity);
let six = slab.insert(6);
assert!(slab.first_free_slot.is_none());
assert_eq!(slab.generation, 0);
assert_eq!(slab.data.len(), 1);
assert_eq!(slab.data.capacity(), capacity);
assert_eq!(six.generation, 0);
assert_eq!(six.index, 0);
let seven = slab.insert(7);
assert!(slab.first_free_slot.is_none());
assert_eq!(slab.generation, 0);
assert_eq!(slab.data.len(), 2);
assert_eq!(slab.data.capacity(), capacity);
assert_eq!(seven.generation, 0);
assert_eq!(seven.index, 1);
let eight = slab.insert(8);
assert!(slab.first_free_slot.is_none());
assert_eq!(slab.generation, 0);
assert_eq!(slab.data.len(), 3);
assert!(slab.data.capacity() >= capacity);
assert_eq!(eight.generation, 0);
assert_eq!(eight.index, 2);
}
#[test]
fn remove_basic() {
let mut slab = Slab::new(5);
let _six = slab.insert(6);
let seven = slab.insert(7);
let _eight = slab.insert(8);
// |6|7|8|
let seven_rem = slab.remove(seven);
// |6|.|8|
assert!(seven_rem.is_some());
assert_eq!(seven_rem.unwrap(), 7);
assert_eq!(slab.first_free_slot.unwrap_or(10), 1);
assert_eq!(slab.generation, 1);
let six_slot = slab.data.get(0);
assert!(six_slot.is_some());
match six_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &6);
assert_eq!(generation, &0);
}
}
let seven_slot = slab.data.get(1);
assert!(seven_slot.is_some());
match seven_slot.unwrap() {
Slot::Empty { next_free_slot } => {
assert!(next_free_slot.is_none());
}
Slot::Filled { .. } => {
panic!("Slot should be empty after call to remove.");
}
}
let eight_slot = slab.data.get(2);
assert!(eight_slot.is_some());
match eight_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &8);
assert_eq!(generation, &0);
}
}
}
#[test]
fn double_remove() {
let mut slab = Slab::new(5);
let _six = slab.insert(6);
let seven = slab.insert(7);
let _eight = slab.insert(8);
// |6|7|8|
let seven_rem = slab.remove(seven);
// |6|.|8|
assert!(seven_rem.is_some());
assert_eq!(seven_rem.unwrap(), 7);
let seven_again = slab.remove(seven);
assert!(seven_again.is_none());
}
#[test]
fn remove_multiple() {
let mut slab = Slab::new(5);
let _six = slab.insert(6);
let seven = slab.insert(7);
let eight = slab.insert(8);
// |6|7|8|
let seven_rem = slab.remove(seven);
// |6|.|8|
assert!(seven_rem.is_some());
assert_eq!(seven_rem.unwrap(), 7);
assert_eq!(slab.first_free_slot.unwrap_or(10), 1);
assert_eq!(slab.generation, 1);
let six_slot = slab.data.get(0);
assert!(six_slot.is_some());
match six_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &6);
assert_eq!(generation, &0);
}
}
let seven_slot = slab.data.get(1);
assert!(seven_slot.is_some());
match seven_slot.unwrap() {
Slot::Empty { next_free_slot } => {
assert!(next_free_slot.is_none());
}
Slot::Filled { .. } => {
panic!("Slot should be empty after call to remove.");
}
}
let eight_slot = slab.data.get(2);
assert!(eight_slot.is_some());
match eight_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &8);
assert_eq!(generation, &0);
}
}
let eight_rem = slab.remove(eight);
// |6|.|.|
assert!(eight_rem.is_some());
assert_eq!(eight_rem.unwrap(), 8);
assert_eq!(slab.first_free_slot.unwrap_or(10), 2);
assert_eq!(slab.generation, 2);
let six_slot = slab.data.get(0);
assert!(six_slot.is_some());
match six_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &6);
assert_eq!(generation, &0);
}
}
let seven_slot = slab.data.get(1);
assert!(seven_slot.is_some());
match seven_slot.unwrap() {
Slot::Empty { next_free_slot } => {
assert!(next_free_slot.is_none());
}
Slot::Filled { .. } => {
panic!("Slot should be empty after call to remove.");
}
}
let eight_slot = slab.data.get(2);
assert!(eight_slot.is_some());
match eight_slot.unwrap() {
Slot::Empty { next_free_slot } => {
assert!(next_free_slot.is_some());
assert_eq!(next_free_slot.unwrap(), 1);
}
Slot::Filled { .. } => {
panic!("Slot should be empty after call to remove.");
}
}
}
#[test]
fn remove_and_reinsert() {
let mut slab = Slab::new(5);
let _six = slab.insert(6);
let seven = slab.insert(7);
let eight = slab.insert(8);
// |6|7|8|
let seven_rem = slab.remove(seven);
// |6|.|8|
assert!(seven_rem.is_some());
assert_eq!(seven_rem.unwrap(), 7);
assert_eq!(slab.first_free_slot.unwrap_or(10), 1);
assert_eq!(slab.generation, 1);
let eight_rem = slab.remove(eight);
// |6|.|.|
assert!(eight_rem.is_some());
assert_eq!(eight_rem.unwrap(), 8);
assert_eq!(slab.first_free_slot.unwrap_or(10), 2);
assert_eq!(slab.generation, 2);
let nine = slab.insert(9);
// |6|.|9|
assert_eq!(nine.index, 2);
assert_eq!(nine.generation, 2);
let eight_again = slab.remove(eight);
assert!(eight_again.is_none());
let six_slot = slab.data.get(0);
assert!(six_slot.is_some());
match six_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &6);
assert_eq!(generation, &0);
}
}
let seven_slot = slab.data.get(1);
assert!(seven_slot.is_some());
match seven_slot.unwrap() {
Slot::Empty { next_free_slot } => {
assert!(next_free_slot.is_none());
}
Slot::Filled { .. } => {
panic!("Slot should be empty after call to remove.");
}
}
let nine_slot = slab.data.get(2);
assert!(nine_slot.is_some());
match nine_slot.unwrap() {
Slot::Empty { .. } => {
panic!("Slot should be filled after call to insert.");
}
Slot::Filled { item, generation } => {
assert_eq!(item, &9);
assert_eq!(generation, &2);
}
}
}
#[test]
fn remove_with_bad_index() {
let mut slab = Slab::new(5);
let _six = slab.insert(6);
let _seven = slab.insert(7);
let mut eight = slab.insert(8);
// |0|1|2| index
// |6|7|8| value
eight.index = 3; // oops, this should be 2
let eight_rem = slab.remove(eight);
assert!(eight_rem.is_none());
}
#[test]
fn get() {
let mut slab = Slab::new(5);
let six = slab.insert(6);
assert_eq!(six.index, 0);
assert_eq!(six.generation, 0);
let seven = slab.insert(7);
assert_eq!(seven.index, 1);
assert_eq!(seven.generation, 0);
let six_ref = slab.get(six);
assert!(six_ref.is_some());
assert_eq!(six_ref.unwrap(), &6);
slab.remove(six);
let six_ref = slab.get(six);
assert!(six_ref.is_none());
let eight = slab.insert(8);
assert_eq!(eight.index, 0);
assert_eq!(eight.generation, 1);
let eight_ref = slab.get(eight);
assert!(eight_ref.is_some());
assert_eq!(eight_ref.unwrap(), &8);
let six_ref = slab.get(six);
assert!(six_ref.is_none());
}
#[test]
fn get_mut() {
let mut slab = Slab::new(5);
let six = slab.insert(6);
assert_eq!(six.index, 0);
assert_eq!(six.generation, 0);
let seven = slab.insert(7);
assert_eq!(seven.index, 1);
assert_eq!(seven.generation, 0);
let six_mut = slab.get_mut(six);
assert!(six_mut.is_some());
let six_mut = six_mut.unwrap();
assert_eq!(six_mut, &mut 6);
*six_mut = 60;
assert_eq!(six_mut, &mut 60);
slab.remove(six);
let six_ref = slab.get_mut(six);
assert!(six_ref.is_none());
let eight = slab.insert(8);
assert_eq!(eight.index, 0);
assert_eq!(eight.generation, 1);
let eight_ref = slab.get_mut(eight);
assert!(eight_ref.is_some());
assert_eq!(eight_ref.unwrap(), &mut 8);
let six_ref = slab.get_mut(six);
assert!(six_ref.is_none());
}
}

813
lib/slab-tree/src/tree.rs Normal file
View File

@@ -0,0 +1,813 @@
use crate::behaviors::*;
use crate::core_tree::CoreTree;
use crate::node::*;
use crate::NodeId;
///
/// A `Tree` builder. Provides more control over how a `Tree` is created.
///
pub struct TreeBuilder<T> {
root: Option<T>,
capacity: Option<usize>,
}
impl<T> Default for TreeBuilder<T> {
fn default() -> Self {
TreeBuilder::new()
}
}
impl<T> TreeBuilder<T> {
///
/// Creates a new `TreeBuilder` with the default settings.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let _tree_builder = TreeBuilder::new();
///
/// # _tree_builder.with_root(1);
/// ```
///
pub fn new() -> TreeBuilder<T> {
TreeBuilder {
root: None,
capacity: None,
}
}
///
/// Sets the root `Node` of the `TreeBuilder`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let _tree_builder = TreeBuilder::new().with_root(1);
/// ```
///
pub fn with_root(self, root: T) -> TreeBuilder<T> {
TreeBuilder {
root: Some(root),
capacity: self.capacity,
}
}
///
/// Sets the capacity of the `TreeBuilder`.
///
/// This can be used to pre-allocate space in the `Tree` if you know you'll be adding a large
/// number of `Node`s to the `Tree`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let _tree_builder = TreeBuilder::new().with_capacity(10);
///
/// # _tree_builder.with_root(1);
/// ```
///
pub fn with_capacity(self, capacity: usize) -> TreeBuilder<T> {
TreeBuilder {
root: self.root,
capacity: Some(capacity),
}
}
///
/// Build a `Tree` based upon the current settings in the `TreeBuilder`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let _tree = TreeBuilder::new().with_root(1).with_capacity(10).build();
/// ```
///
pub fn build(self) -> Tree<T> {
let capacity = self.capacity.unwrap_or(0);
let mut core_tree: CoreTree<T> = CoreTree::new(capacity);
let root_id = self.root.map(|val| core_tree.insert(val));
Tree { root_id, core_tree }
}
}
///
/// A tree structure containing `Node`s.
///
#[derive(Debug, PartialEq)]
pub struct Tree<T> {
pub(crate) root_id: Option<NodeId>,
pub(crate) core_tree: CoreTree<T>,
}
impl<T> Tree<T> {
///
/// Creates a new `Tree` with a capacity of 0.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let tree: Tree<i32> = Tree::new();
///
/// # assert_eq!(tree.capacity(), 0);
/// ```
///
pub fn new() -> Tree<T> {
TreeBuilder::new().build()
}
//todo: write test for this
///
/// Sets the "root" of the `Tree` to be `root`.
///
/// If there is already a "root" node in the `Tree`, that node is shifted down and the new
/// one takes its place.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
///
/// let root_id = tree.set_root(1);
///
/// assert_eq!(tree.root_id().unwrap(), root_id);
/// ```
///
pub fn set_root(&mut self, root: T) -> NodeId {
let old_root_id = self.root_id.take();
let new_root_id = self.core_tree.insert(root);
self.root_id = Some(new_root_id);
self.set_first_child(new_root_id, old_root_id);
self.set_last_child(new_root_id, old_root_id);
if let Some(node_id) = old_root_id {
self.set_parent(node_id, self.root_id);
}
new_root_id
}
///
/// Returns the `Tree`'s current capacity. Capacity is defined as the number of times new
/// `Node`s can be added to the `Tree` before it must allocate more memory.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let tree: Tree<i32> = Tree::new();
///
/// assert_eq!(tree.capacity(), 0);
/// ```
///
pub fn capacity(&self) -> usize {
self.core_tree.capacity()
}
///
/// Returns the `NodeId` of the root node of the `Tree`.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
/// tree.set_root(1);
///
/// let root_id = tree.root_id().expect("root doesn't exist?");
///
/// assert_eq!(tree.get(root_id).unwrap().data(), &1);
/// ```
///
pub fn root_id(&self) -> Option<NodeId> {
self.root_id
}
///
/// Returns a `NodeRef` pointing to the root `Node` of the `Tree`.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
/// tree.set_root(1);
///
/// let root = tree.root().expect("root doesn't exist?");
///
/// assert_eq!(root.data(), &1);
/// ```
///
pub fn root(&self) -> Option<NodeRef<T>> {
self.root_id.map(|id| self.new_node_ref(id))
}
///
/// Returns a `NodeMut` pointing to the root `Node` of the `Tree`.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
/// tree.set_root(1);
///
/// let mut root = tree.root_mut().expect("root doesn't exist?");
/// assert_eq!(root.data(), &mut 1);
///
/// *root.data() = 2;
/// assert_eq!(root.data(), &mut 2);
/// ```
///
pub fn root_mut(&mut self) -> Option<NodeMut<T>> {
self.root_id.map(move |id| self.new_node_mut(id))
}
///
/// Returns the `NodeRef` pointing to the `Node` that the given `NodeId` identifies. If the
/// `NodeId` in question points to nothing (or belongs to a different `Tree`) a `None`-value
/// will be returned; otherwise, a `Some`-value will be returned.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
/// tree.set_root(1);
/// let root_id = tree.root_id().expect("root doesn't exist?");
///
/// let root = tree.get(root_id);
/// assert!(root.is_some());
///
/// let root = root.unwrap();
/// assert_eq!(root.data(), &1);
/// ```
///
pub fn get(&self, node_id: NodeId) -> Option<NodeRef<T>> {
let _ = self.core_tree.get(node_id)?;
Some(self.new_node_ref(node_id))
}
///
/// Returns the `NodeMut` pointing to the `Node` that the given `NodeId` identifies. If the
/// `NodeId` in question points to nothing (or belongs to a different `Tree`) a `None`-value
/// will be returned; otherwise, a `Some`-value will be returned.
///
/// ```
/// use slab_tree::tree::Tree;
///
/// let mut tree = Tree::new();
/// tree.set_root(1);
/// let root_id = tree.root_id().expect("root doesn't exist?");
///
/// let root = tree.get_mut(root_id);
/// assert!(root.is_some());
///
/// let mut root = root.unwrap();
///
/// *root.data() = 2;
/// assert_eq!(root.data(), &mut 2);
/// ```
///
pub fn get_mut(&mut self, node_id: NodeId) -> Option<NodeMut<T>> {
let _ = self.core_tree.get_mut(node_id)?;
Some(self.new_node_mut(node_id))
}
///
/// Remove a `Node` by its `NodeId` and return the data that it contained.
/// Returns a `Some`-value if the `Node` exists; returns a `None`-value otherwise.
///
/// Children of the removed `Node` can either be dropped with `DropChildren` or orphaned with
/// `OrphanChildren`.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
/// use slab_tree::behaviors::RemoveBehavior::*;
///
/// let mut tree = TreeBuilder::new().with_root(1).build();
/// let two_id = {
/// let mut root = tree.root_mut().expect("root doesn't exist?");
/// let two_id = root.append(2).node_id();
/// root.append(3);
/// two_id
/// };
///
/// let two = tree.remove(two_id, DropChildren);
///
/// assert!(two.is_some());
/// assert_eq!(two.unwrap(), 2);
///
/// let root = tree.root().expect("root doesn't exist?");
/// assert!(root.first_child().is_some());
/// assert_eq!(root.first_child().unwrap().data(), &mut 3);
///
/// assert!(root.last_child().is_some());
/// assert_eq!(root.last_child().unwrap().data(), &mut 3);
/// ```
///
pub fn remove(&mut self, node_id: NodeId, behavior: RemoveBehavior) -> Option<T> {
if let Some(node) = self.get_node(node_id) {
let Relatives {
parent,
prev_sibling,
next_sibling,
..
} = node.relatives;
let (is_first_child, is_last_child) = self.is_node_first_last_child(node_id);
if is_first_child {
// parent first child = my next sibling
self.set_first_child(parent.expect("parent must exist"), next_sibling);
}
if is_last_child {
// parent last child = my prev sibling
self.set_last_child(parent.expect("parent must exist"), prev_sibling);
}
if let Some(prev) = prev_sibling {
self.set_next_sibling(prev, next_sibling);
}
if let Some(next) = next_sibling {
self.set_prev_sibling(next, prev_sibling);
}
match behavior {
RemoveBehavior::DropChildren => self.drop_children(node_id),
RemoveBehavior::OrphanChildren => self.orphan_children(node_id),
};
if self.root_id == Some(node_id) {
self.root_id = None;
}
self.core_tree.remove(node_id)
} else {
None
}
}
pub(crate) fn get_node(&self, node_id: NodeId) -> Option<&Node<T>> {
self.core_tree.get(node_id)
}
pub(crate) fn get_node_mut(&mut self, node_id: NodeId) -> Option<&mut Node<T>> {
self.core_tree.get_mut(node_id)
}
pub(crate) fn set_prev_siblings_next_sibling(
&mut self,
current_id: NodeId,
next_sibling: Option<NodeId>,
) {
if let Some(prev_sibling_id) = self.get_node_prev_sibling_id(current_id) {
self.set_next_sibling(prev_sibling_id, next_sibling);
}
}
pub(crate) fn set_next_siblings_prev_sibling(
&mut self,
current_id: NodeId,
prev_sibling: Option<NodeId>,
) {
if let Some(next_sibling_id) = self.get_node_next_sibling_id(current_id) {
self.set_prev_sibling(next_sibling_id, prev_sibling);
}
}
pub(crate) fn set_parent(&mut self, node_id: NodeId, parent_id: Option<NodeId>) {
if let Some(node) = self.get_node_mut(node_id) {
node.relatives.parent = parent_id;
} else {
unreachable!()
}
}
pub(crate) fn set_prev_sibling(&mut self, node_id: NodeId, prev_sibling: Option<NodeId>) {
if let Some(node) = self.get_node_mut(node_id) {
node.relatives.prev_sibling = prev_sibling;
} else {
unreachable!()
}
}
pub(crate) fn set_next_sibling(&mut self, node_id: NodeId, next_sibling: Option<NodeId>) {
if let Some(node) = self.get_node_mut(node_id) {
node.relatives.next_sibling = next_sibling;
} else {
unreachable!()
}
}
pub(crate) fn set_first_child(&mut self, node_id: NodeId, first_child: Option<NodeId>) {
if let Some(node) = self.get_node_mut(node_id) {
node.relatives.first_child = first_child;
} else {
unreachable!()
}
}
pub(crate) fn set_last_child(&mut self, node_id: NodeId, last_child: Option<NodeId>) {
if let Some(node) = self.get_node_mut(node_id) {
node.relatives.last_child = last_child;
} else {
unreachable!()
}
}
pub(crate) fn get_node_prev_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
if let Some(node) = self.get_node(node_id) {
node.relatives.prev_sibling
} else {
unreachable!()
}
}
pub(crate) fn get_node_next_sibling_id(&self, node_id: NodeId) -> Option<NodeId> {
if let Some(node) = self.get_node(node_id) {
node.relatives.next_sibling
} else {
unreachable!()
}
}
pub(crate) fn get_node_relatives(&self, node_id: NodeId) -> Relatives {
if let Some(node) = self.get_node(node_id) {
node.relatives
} else {
unreachable!()
}
}
fn drop_children(&mut self, node_id: NodeId) {
let sub_tree_ids: Vec<NodeId> = self
.get(node_id)
.expect("node must exist")
.traverse_level_order()
.skip(1) // skip the "root" of the sub-tree, which is the "current" node
.map(|node_ref| node_ref.node_id())
.collect();
for id in sub_tree_ids {
self.core_tree.remove(id);
}
}
fn orphan_children(&mut self, node_id: NodeId) {
let child_ids: Vec<NodeId> = self
.get(node_id)
.expect("node must exist")
.children()
.map(|node_ref| node_ref.node_id())
.collect();
for id in child_ids {
self.set_parent(id, None);
}
}
fn new_node_ref(&self, node_id: NodeId) -> NodeRef<T> {
NodeRef::new(node_id, self)
}
fn new_node_mut(&mut self, node_id: NodeId) -> NodeMut<T> {
NodeMut::new(node_id, self)
}
fn is_node_first_last_child(&self, node_id: NodeId) -> (bool, bool) {
if let Some(node) = self.get_node(node_id) {
node.relatives
.parent
.and_then(|parent_id| self.get_node(parent_id))
.map(|parent| {
let Relatives {
first_child: first,
last_child: last,
..
} = parent.relatives;
(
first.map(|child_id| child_id == node_id).unwrap_or(false),
last.map(|child_id| child_id == node_id).unwrap_or(false),
)
})
.unwrap_or((false, false))
} else {
(false, false)
}
}
}
impl<T> Default for Tree<T> {
fn default() -> Self {
TreeBuilder::new().build()
}
}
impl<T: std::fmt::Debug> Tree<T> {
/// Write formatted tree representation and nodes with debug formatting.
///
/// Example:
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let mut tree = TreeBuilder::new().with_root(0).build();
/// let mut root = tree.root_mut().unwrap();
/// root.append(1)
/// .append(2);
/// root.append(3);
/// let mut s = String::new();
/// tree.write_formatted(&mut s).unwrap();
/// assert_eq!(&s, "\
/// 0
/// ├── 1
/// │ └── 2
/// └── 3
/// ");
/// ```
///
/// Writes nothing if the tree is empty.
///
/// ```
/// use slab_tree::tree::TreeBuilder;
///
/// let tree = TreeBuilder::<i32>::new().build();
/// let mut s = String::new();
/// tree.write_formatted(&mut s).unwrap();
/// assert_eq!(&s, "");
/// ```
pub fn write_formatted<W: std::fmt::Write>(&self, w: &mut W) -> std::fmt::Result {
if let Some(root) = self.root() {
let node_id = root.node_id();
let childn = 0;
let level = 0;
let last = vec![];
let mut stack = vec![(node_id, childn, level, last)];
while let Some((node_id, childn, level, last)) = stack.pop() {
debug_assert_eq!(
last.len(),
level,
"each previous level should indicate whether it has reached the last node"
);
let node = self
.get(node_id)
.expect("getting node of existing node ref id");
if childn == 0 {
for i in 1..level {
if last[i - 1] {
write!(w, " ")?;
} else {
write!(w, "")?;
}
}
if level > 0 {
if last[level - 1] {
write!(w, "└── ")?;
} else {
write!(w, "├── ")?;
}
}
writeln!(w, "{:?}", node.data())?;
}
let mut children = node.children().skip(childn);
if let Some(child) = children.next() {
let mut next_last = last.clone();
if children.next().is_some() {
stack.push((node_id, childn + 1, level, last));
next_last.push(false);
} else {
next_last.push(true);
}
stack.push((child.node_id(), 0, level + 1, next_last));
}
}
}
Ok(())
}
}
#[cfg_attr(tarpaulin, skip)]
#[cfg(test)]
mod tree_tests {
use super::*;
use crate::behaviors::RemoveBehavior::{DropChildren, OrphanChildren};
#[test]
fn capacity() {
let tree = TreeBuilder::new().with_root(1).with_capacity(5).build();
assert_eq!(tree.capacity(), 5);
}
#[test]
fn root_id() {
let tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
let root = tree.get(root_id).unwrap();
assert_eq!(root.data(), &1);
}
#[test]
fn remove_root_drop() {
let mut tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
tree.remove(root_id, RemoveBehavior::DropChildren);
assert!(tree.root().is_none());
}
#[test]
fn remove_root_orphan() {
let mut tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
tree.remove(root_id, RemoveBehavior::OrphanChildren);
assert!(tree.root().is_none());
}
#[test]
fn root() {
let tree = TreeBuilder::new().with_root(1).build();
let root = tree.root().expect("root doesn't exist?");
assert_eq!(root.data(), &1);
}
#[test]
fn root_mut() {
let mut tree = TreeBuilder::new().with_root(1).build();
let mut root = tree.root_mut().expect("root doesn't exist?");
assert_eq!(root.data(), &mut 1);
*root.data() = 2;
assert_eq!(root.data(), &mut 2);
}
#[test]
fn get() {
let tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
let root = tree.get(root_id);
assert!(root.is_some());
let root = root.unwrap();
assert_eq!(root.data(), &1);
}
#[test]
fn get_mut() {
let mut tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
let root = tree.get_mut(root_id);
assert!(root.is_some());
let mut root = root.unwrap();
assert_eq!(root.data(), &mut 1);
*root.data() = 2;
assert_eq!(root.data(), &mut 2);
}
#[test]
fn get_node() {
let tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
let root = tree.get_node(root_id);
assert!(root.is_some());
let root = root.unwrap();
assert_eq!(root.data, 1);
}
#[test]
fn get_node_mut() {
let mut tree = TreeBuilder::new().with_root(1).build();
let root_id = tree.root_id().expect("root doesn't exist?");
let root = tree.get_node_mut(root_id);
assert!(root.is_some());
let root = root.unwrap();
assert_eq!(root.data, 1);
root.data = 2;
assert_eq!(root.data, 2);
}
#[test]
fn remove_drop() {
let mut tree = TreeBuilder::new().with_root(1).build();
let two_id;
let three_id;
let four_id;
let five_id;
{
let mut root = tree.root_mut().expect("root doesn't exist?");
two_id = root.append(2).node_id();
three_id = root.append(3).node_id();
four_id = root.append(4).node_id();
}
{
five_id = tree
.get_mut(three_id)
.expect("three doesn't exist?")
.append(5)
.node_id();
}
// 1
// / | \
// 2 3 4
// |
// 5
tree.remove(three_id, DropChildren);
let root = tree
.get_node(tree.root_id().expect("tree doesn't exist?"))
.unwrap();
assert!(root.relatives.first_child.is_some());
assert!(root.relatives.last_child.is_some());
assert_eq!(root.relatives.first_child.unwrap(), two_id);
assert_eq!(root.relatives.last_child.unwrap(), four_id);
let two = tree.get_node(two_id);
assert!(two.is_some());
let two = two.unwrap();
assert_eq!(two.relatives.next_sibling, Some(four_id));
let four = tree.get_node(four_id);
assert!(four.is_some());
let four = four.unwrap();
assert_eq!(four.relatives.prev_sibling, Some(two_id));
let five = tree.get_node(five_id);
assert!(five.is_none());
}
/// Test that there is no panic if caller tries to remove a removed node
#[test]
fn address_dropped() {
let mut tree = TreeBuilder::new().with_root(1).build();
let two_id = tree.root_mut().expect("root doesn't exist").node_id();
tree.remove(two_id, DropChildren);
tree.remove(two_id, DropChildren);
}
#[test]
fn remove_orphan() {
let mut tree = TreeBuilder::new().with_root(1).build();
let two_id;
let three_id;
let four_id;
let five_id;
{
let mut root = tree.root_mut().expect("root doesn't exist?");
two_id = root.append(2).node_id();
three_id = root.append(3).node_id();
four_id = root.append(4).node_id();
}
{
five_id = tree
.get_mut(three_id)
.expect("three doesn't exist?")
.append(5)
.node_id();
}
// 1
// / | \
// 2 3 4
// |
// 5
tree.remove(three_id, OrphanChildren);
let root = tree
.get_node(tree.root_id().expect("tree doesn't exist?"))
.unwrap();
assert!(root.relatives.first_child.is_some());
assert!(root.relatives.last_child.is_some());
assert_eq!(root.relatives.first_child.unwrap(), two_id);
assert_eq!(root.relatives.last_child.unwrap(), four_id);
let two = tree.get_node(two_id);
assert!(two.is_some());
let two = two.unwrap();
assert_eq!(two.relatives.next_sibling, Some(four_id));
let four = tree.get_node(four_id);
assert!(four.is_some());
let four = four.unwrap();
assert_eq!(four.relatives.prev_sibling, Some(two_id));
let five = tree.get_node(five_id);
assert!(five.is_some());
let five = five.unwrap();
assert_eq!(five.relatives.parent, None);
}
}

View File

@@ -1,64 +1,77 @@
use std::path::Path; use std::io;
use crossterm::event::{Event, KeyEventKind}; use crossterm::event::{Event, KeyEventKind};
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::{Environment, State}; use crate::state::{Environment, State};
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::init::StateInitializer;
use crate::state::view::View; use crate::state::view::View;
pub fn run(start_path: &Path) -> std::io::Result<()> { pub fn run(state_initializer: &StateInitializer, view: &mut View) -> io::Result<()> {
View::restore_terminal_on_panic(); let environment = Environment::try_from(&*view)?;
let mut view = View::stdout()?; let mut state = State::new(state_initializer, environment);
let environment = Environment::try_from(&view)?; loop {
let mut state = State::with_root_path(start_path, environment); match state.handle_events() {
EventResult::Nothing => {}
EventResult::Draw => {
view.set_dirty(false);
}
EventResult::Redraw => {
view.set_dirty(true);
}
}
'render: loop {
view.render(|frame| state.render(frame))?; view.render(|frame| state.render(frame))?;
'event: loop { match handle_terminal_event(&mut state, crossterm::event::read()?) {
match handle_event(&mut state, crossterm::event::read()?) {
ActionResult::Nothing => { ActionResult::Nothing => {
continue 'event; continue;
} }
ActionResult::Draw => { ActionResult::Draw => {
continue 'render; view.set_dirty(false);
continue;
} }
ActionResult::Redraw => { ActionResult::Redraw => {
view.clear()?; view.set_dirty(true);
continue 'render; continue;
} }
ActionResult::PushLayer(layer) => { ActionResult::PushLayer(layer) => {
state.push_layer(layer); state.push_layer(layer);
continue 'render; view.set_dirty(false);
continue;
} }
ActionResult::ReplaceLayer(layer) => { ActionResult::ReplaceLayer(layer) => {
state.pop_layer(); state.pop_layer();
state.push_layer(layer); state.push_layer(layer);
continue 'render; view.set_dirty(false);
continue;
} }
ActionResult::PopLayer => { ActionResult::PopLayer => {
if state.pop_layer() { if state.pop_layer() {
break 'render; break;
} else { } else {
continue 'render; view.set_dirty(false);
} continue;
} }
} }
} }
} }
view.close() Ok(())
} }
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]
fn handle_event(state: &mut State, event: Event) -> ActionResult { fn handle_terminal_event(state: &mut State, event: Event) -> ActionResult {
if let Event::Key(key) = event { if let Event::Key(key) = event {
if key.kind == KeyEventKind::Release { if key.kind == KeyEventKind::Release {
ActionResult::Nothing ActionResult::Nothing

View File

@@ -69,7 +69,7 @@ impl<'a> InputFieldDialogBuilder5<'a> {
self self
} }
pub fn build(self, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> InputFieldDialogLayer<'a> { pub fn on_confirm<F>(self, confirm_action: F) -> InputFieldDialogLayer<'a> where F: Fn(String) -> ActionResult + 'static {
let step4 = self.step4; let step4 = self.step4;
let step3 = step4.step3; let step3 = step4.step3;
let step2 = step3.step2; let step2 = step3.step2;

View File

@@ -12,6 +12,7 @@ use crate::component::input::InputField;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@@ -29,8 +30,9 @@ pub struct InputFieldDialogLayer<'a> {
} }
impl<'a> InputFieldDialogLayer<'a> { impl<'a> InputFieldDialogLayer<'a> {
fn new(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self { fn new<F>(y: u16, min_width: u16, default_color: Color, darker_color: Color, title: Line<'a>, message: Text<'a>, initial_value: Option<String>, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
let field = initial_value.map_or_else(InputField::new, InputField::with_text); let field = initial_value.map_or_else(InputField::new, InputField::with_text);
let confirm_action = Box::new(confirm_action);
Self { y, min_width, default_color, darker_color, title, message, field, confirm_action } Self { y, min_width, default_color, darker_color, title, message, field, confirm_action }
} }
@@ -58,6 +60,10 @@ impl<'a> Layer for InputFieldDialogLayer<'a> {
} }
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX); let message_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX); let message_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);

View File

@@ -37,9 +37,9 @@ impl<'a> MessageDialogActionMap<'a> {
]) ])
} }
pub fn yes_no(yes_action: Box<dyn Fn() -> ActionResult>) -> Self { pub fn yes_no<F>(yes_action: F) -> Self where F: Fn() -> ActionResult + 'static {
let mut map = ActionHashMap::new(); let mut map = ActionHashMap::new();
map.insert(KeyBinding::char('y'), yes_action); map.insert(KeyBinding::char('y'), Box::new(yes_action));
map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer)); map.insert(KeyBinding::char('n'), Box::new(|| ActionResult::PopLayer));
Self::new(map, vec![ Self::new(map, vec![

View File

@@ -63,7 +63,7 @@ impl<'a> MessageDialogBuilder4<'a> {
self.actions(MessageDialogActionMap::ok()) self.actions(MessageDialogActionMap::ok())
} }
pub fn yes_no(self, yes_action: Box<dyn Fn() -> ActionResult>) -> MessageDialogLayer<'a> { pub fn yes_no<F>(self, yes_action: F) -> MessageDialogLayer<'a> where F: Fn() -> ActionResult + 'static {
self.actions(MessageDialogActionMap::yes_no(yes_action)) self.actions(MessageDialogActionMap::yes_no(yes_action))
} }
} }

View File

@@ -10,6 +10,7 @@ use crate::component::dialog::render_dialog_border;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@@ -40,7 +41,7 @@ impl<'a> MessageDialogLayer<'a> {
MessageDialogBuilder MessageDialogBuilder
} }
pub fn generic_error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> { pub fn error(y: u16, message: impl Into<Text<'a>>) -> MessageDialogLayer<'a> {
Self::build() Self::build()
.y(y) .y(y)
.color(Color::LightRed) .color(Color::LightRed)
@@ -55,6 +56,10 @@ impl Layer for MessageDialogLayer<'_> {
self.actions.handle_input(key_binding) self.actions.handle_input(key_binding)
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX); let content_width = u16::try_from(self.message.width()).unwrap_or(u16::MAX);
let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX); let content_height = u16::try_from(self.message.height()).unwrap_or(u16::MAX);

View File

@@ -1,4 +1,5 @@
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::input::InputFieldOverlayLayer;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
@@ -17,3 +18,14 @@ impl Action<FsLayer> for RedrawScreen {
ActionResult::Redraw ActionResult::Redraw
} }
} }
pub struct EnterCommandMode;
impl Action<FsLayer> for EnterCommandMode {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
ActionResult::push_layer(InputFieldOverlayLayer::new(":", move |command| {
// command.split_once(" ")
ActionResult::PopLayer
}))
}
}

View File

@@ -17,7 +17,7 @@ impl Action<FsLayer> for PushCountDigit {
if new_count > MAX_COUNT { if new_count > MAX_COUNT {
layer.registers.count = None; layer.registers.count = None;
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset.")))) ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Count is too large (> {MAX_COUNT}), it will be reset.")))
} else { } else {
layer.registers.count = Some(new_count); layer.registers.count = Some(new_count);
ActionResult::Nothing ActionResult::Nothing

View File

@@ -1,19 +1,17 @@
use std::{fs, io}; use std::{fs, io};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc;
use ratatui::style::Color; use ratatui::style::Color;
use slab_tree::NodeId; use slab_tree::NodeId;
use crate::component::dialog::input::InputFieldDialogLayer; use crate::component::dialog::input::InputFieldDialogLayer;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::file::FileKind; use crate::file::FileKind;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
trait CreateEntry { trait CreateEntry {
fn title() -> &'static str; fn title() -> &'static str;
@@ -71,7 +69,7 @@ impl Action<FsLayer> for CreateDirectoryInSelectedDirectory {
fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult { fn create_in_selected_directory<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) { if let Some(FileNode { node, path, .. }) = get_selected_file(layer).filter(|n| matches!(n.entry.kind(), FileKind::Directory)) {
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))) ActionResult::push_layer(create_new_name_prompt::<T>(layer, path.to_owned(), node.node_id()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@@ -95,7 +93,7 @@ impl Action<FsLayer> for CreateDirectoryInParentOfSelectedEntry {
fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult { fn create_in_parent_of_selected_file<T: CreateEntry>(layer: &mut FsLayer) -> ActionResult {
if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) { if let Some((parent_node_id, parent_path)) = get_parent_of_selected_file(layer) {
ActionResult::PushLayer(Box::new(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))) ActionResult::push_layer(create_new_name_prompt::<T>(layer, parent_path.to_owned(), parent_node_id))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@@ -105,9 +103,9 @@ fn get_parent_of_selected_file(layer: &FsLayer) -> Option<(NodeId, &Path)> {
get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) }) get_selected_file(layer).and_then(|n| { Some((n.node.parent_id()?, n.path.parent()?)) })
} }
fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, view_node_id_to_refresh: NodeId) -> InputFieldDialogLayer<'b> { fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: PathBuf, parent_view_node_id: NodeId) -> InputFieldDialogLayer<'b> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let pending_events = Rc::clone(&layer.pending_events); let events = layer.events();
InputFieldDialogLayer::build() InputFieldDialogLayer::build()
.y(y) .y(y)
@@ -115,25 +113,24 @@ fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: Pa
.color(Color::LightCyan, Color::Cyan) .color(Color::LightCyan, Color::Cyan)
.title(T::title()) .title(T::title())
.message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy())) .message(format!("Creating {} in {}", T::kind(), parent_folder.to_string_lossy()))
.build(Box::new(move |new_name| { .on_confirm(move |new_name| {
if new_name.is_empty() { if new_name.is_empty() {
return ActionResult::Nothing; return ActionResult::Nothing;
} }
let new_path = parent_folder.join(&new_name); let new_path = parent_folder.join(&new_name);
if new_path.exists() { if new_path.exists() {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, "Something with this name already exists."))); return ActionResult::push_layer(MessageDialogLayer::error(y, "Something with this name already exists."));
} }
match T::create(new_path) { match T::create(new_path) {
Ok(_) => { Ok(_) => {
FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events); events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y, format!("Could not create {}: {e}", T::kind())))) ActionResult::push_layer(MessageDialogLayer::error(y, format!("Could not create {}: {e}", T::kind())))
} }
} }
})) })
} }

View File

@@ -1,6 +1,5 @@
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ratatui::style::Color; use ratatui::style::Color;
@@ -9,18 +8,18 @@ use slab_tree::NodeId;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_entry_kind_name, get_selected_file};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::file::{FileEntry, FileKind}; use crate::file::{FileEntry, FileKind};
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
pub struct DeleteSelectedEntry; pub struct DeleteSelectedEntry;
impl Action<FsLayer> for DeleteSelectedEntry { impl Action<FsLayer> for DeleteSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) { if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
ActionResult::PushLayer(Box::new(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))) ActionResult::push_layer(create_delete_confirmation_dialog(layer, node.node_id(), entry, path.to_owned()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@@ -29,7 +28,7 @@ impl Action<FsLayer> for DeleteSelectedEntry {
fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> { fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId, entry: &FileEntry, path: PathBuf) -> MessageDialogLayer<'a> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let pending_events = Rc::clone(&layer.pending_events); let events = layer.events();
let total_files = if matches!(entry.kind(), FileKind::Directory) { let total_files = if matches!(entry.kind(), FileKind::Directory) {
count_files(path.clone()) count_files(path.clone())
@@ -45,17 +44,17 @@ fn create_delete_confirmation_dialog<'a>(layer: &FsLayer, view_node_id: NodeId,
Line::from(format!("Permanently delete {}?", path.to_string_lossy())), Line::from(format!("Permanently delete {}?", path.to_string_lossy())),
Line::from(format!("This will affect {}.", total_files.describe())), Line::from(format!("This will affect {}.", total_files.describe())),
]) ])
.yes_no(Box::new(move || { .yes_no(move || {
match delete_path_recursively(&path) { match delete_path_recursively(&path) {
Ok(_) => { Ok(_) => {
FsLayerEvent::DeleteViewNode(view_node_id).enqueue(&pending_events); events.enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.delete_node(view_node_id)));
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::ReplaceLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), e.to_string()))) ActionResult::replace_layer(MessageDialogLayer::error(y.saturating_add(1), e.to_string()))
} }
} }
})) })
} }
const MAX_COUNT_TIME: Duration = Duration::from_secs(5); const MAX_COUNT_TIME: Duration = Duration::from_secs(5);

View File

@@ -8,12 +8,11 @@ use slab_tree::NodeRef;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, get_selected_file};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions; use crate::state::event::EventResult;
pub struct EditSelectedEntry; pub struct EditSelectedEntry;
@@ -34,12 +33,12 @@ fn open_default_editor(layer: &FsLayer, node: &NodeRef<FsTreeViewNode>, path: &P
.status(); .status();
if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) { if status.is_err_and(|e| e.kind() == ErrorKind::NotFound) {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy())))); return ActionResult::push_layer(MessageDialogLayer::error(layer.dialog_y(), format!("Default editor '{}' not found.", editor.to_string_lossy())));
} }
// Refresh the parent directory, or the root node if this is the view root. // Refresh the parent directory, or the root node if this is the view root.
let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id()); let node_id_to_refresh = node.parent_id().unwrap_or_else(|| node.node_id());
FsLayerEvent::RefreshViewNodeChildren(node_id_to_refresh).enqueue(&layer.pending_events); layer.events().enqueue_fn(move |layer, _| EventResult::draw_if(layer.tree.refresh_children(node_id_to_refresh)));
ActionResult::Redraw ActionResult::Redraw
} }

View File

@@ -1,11 +1,13 @@
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use slab_tree::NodeRef; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::{FileEntry, FileKind}; use crate::file::{FileEntry, FileKind};
use crate::state::Environment;
use crate::state::event::{Event, EventResult};
pub use self::create::*; pub use self::create::*;
pub use self::delete::*; pub use self::delete::*;
@@ -18,7 +20,7 @@ mod edit;
mod rename; mod rename;
fn get_selected_file(layer: &FsLayer) -> Option<FileNode> { fn get_selected_file(layer: &FsLayer) -> Option<FileNode> {
if let Some(node) = layer.selected_node() { if let Some(node) = layer.tree.selected_node() {
if let Some(entry) = layer.tree.get_entry(&node) { if let Some(entry) = layer.tree.get_entry(&node) {
if let Some(path) = entry.path() { if let Some(path) = entry.path() {
return Some(FileNode { node, entry, path }); return Some(FileNode { node, entry, path });
@@ -54,3 +56,19 @@ fn format_io_error(err: &io::Error) -> String {
str.push('.'); str.push('.');
str str
} }
struct RefreshParentDirectoryAndSelectFile {
parent_view_node_id: NodeId,
child_file_name: String,
}
impl Event<FsLayer> for RefreshParentDirectoryAndSelectFile {
fn dispatch(&self, layer: &mut FsLayer, _environment: &Environment) -> EventResult {
if layer.tree.refresh_children(self.parent_view_node_id) {
layer.tree.select_child_node_by_name(self.parent_view_node_id, &self.child_file_name);
EventResult::Draw
} else {
EventResult::Nothing
}
}
}

View File

@@ -1,21 +1,19 @@
use io::ErrorKind; use io::ErrorKind;
use std::{fs, io}; use std::{fs, io};
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc;
use ratatui::style::Color; use ratatui::style::Color;
use slab_tree::NodeRef; use slab_tree::NodeRef;
use crate::component::dialog::input::InputFieldDialogLayer; use crate::component::dialog::input::InputFieldDialogLayer;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file}; use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode; use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::FileEntry; use crate::file::FileEntry;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
pub struct RenameSelectedEntry { pub struct RenameSelectedEntry {
pub prefill: bool, pub prefill: bool,
@@ -24,7 +22,7 @@ pub struct RenameSelectedEntry {
impl Action<FsLayer> for RenameSelectedEntry { impl Action<FsLayer> for RenameSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if let Some(FileNode { node, entry, path }) = get_selected_file(layer) { if let Some(FileNode { node, entry, path }) = get_selected_file(layer) {
ActionResult::PushLayer(Box::new(self.create_rename_dialog(layer, &node, entry, path.to_owned()))) ActionResult::push_layer(self.create_rename_dialog(layer, &node, entry, path.to_owned()))
} else { } else {
ActionResult::Nothing ActionResult::Nothing
} }
@@ -35,8 +33,8 @@ impl RenameSelectedEntry {
#[allow(clippy::wildcard_enum_match_arm)] #[allow(clippy::wildcard_enum_match_arm)]
fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> { fn create_rename_dialog<'a, 'b>(&'a self, layer: &'a FsLayer, node: &'a NodeRef<FsTreeViewNode>, entry: &'a FileEntry, path: PathBuf) -> InputFieldDialogLayer<'b> {
let y = layer.dialog_y(); let y = layer.dialog_y();
let parent_node_id = node.parent_id(); let events = layer.events();
let pending_events = Rc::clone(&layer.pending_events); let parent_view_node_id = node.parent_id();
InputFieldDialogLayer::build() InputFieldDialogLayer::build()
.y(y) .y(y)
@@ -45,20 +43,19 @@ impl RenameSelectedEntry {
.title(format!("Rename {}", get_entry_kind_name(entry))) .title(format!("Rename {}", get_entry_kind_name(entry)))
.message(format!("Renaming {}", path.to_string_lossy())) .message(format!("Renaming {}", path.to_string_lossy()))
.initial_value(self.prefill.then(|| entry.name().str().to_owned())) .initial_value(self.prefill.then(|| entry.name().str().to_owned()))
.build(Box::new(move |new_name| { .on_confirm(move |new_name| {
match rename_file(&path, &new_name) { match rename_file(&path, &new_name) {
Ok(_) => { Ok(_) => {
if let Some(parent_node_id) = parent_node_id { if let Some(parent_view_node_id) = parent_view_node_id {
FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events); events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
} }
ActionResult::PopLayer ActionResult::PopLayer
} }
Err(e) => { Err(e) => {
ActionResult::PushLayer(Box::new(MessageDialogLayer::generic_error(y.saturating_add(1), format_io_error(&e)))) ActionResult::push_layer(MessageDialogLayer::error(y.saturating_add(1), format_io_error(&e)))
} }
} }
})) })
} }
} }

View File

@@ -1,104 +1,5 @@
use lazy_static::lazy_static; pub mod application;
pub mod count;
use crate::component::filesystem::action::application::{Quit, RedrawScreen}; pub mod file;
use crate::component::filesystem::action::count::PushCountDigit; pub mod movement;
use crate::component::filesystem::action::file::{CreateDirectoryInParentOfSelectedEntry, CreateDirectoryInSelectedDirectory, CreateFileInParentOfSelectedEntry, CreateFileInSelectedDirectory, DeleteSelectedEntry, EditSelectedEntry, RenameSelectedEntry}; pub mod tree;
use crate::component::filesystem::action::movement::{CollapseSelectedOr, ExpandSelectedOr, MoveBetweenFirstAndLastSibling, MoveDown, MovementWithCountFactory, MovementWithFallbackFactory, MoveOrTraverseUpParent, MoveToFirst, MoveToLast, MoveToLineOr, MoveToNextSibling, MoveToParent, MoveToPreviousSibling, MoveUp, ScreenHeightRatio};
use crate::component::filesystem::action::tree::{ExpandCollapse, RefreshChildrenOfSelected};
use crate::component::filesystem::FsLayer;
use crate::input::keymap::KeyMap;
use crate::state::action::Action;
mod application;
mod count;
mod file;
mod movement;
mod tree;
type ActionKeyMap = KeyMap<Box<dyn Action<FsLayer> + Sync>>;
lazy_static! {
pub static ref ACTION_MAP: ActionKeyMap = create_action_map();
}
fn create_action_map() -> ActionKeyMap {
let mut me = ActionKeyMap::new();
map(&mut me, "0", PushCountDigit(0));
map(&mut me, "1", PushCountDigit(1));
map(&mut me, "2", PushCountDigit(2));
map(&mut me, "3", PushCountDigit(3));
map(&mut me, "4", PushCountDigit(4));
map(&mut me, "5", PushCountDigit(5));
map(&mut me, "6", PushCountDigit(6));
map(&mut me, "7", PushCountDigit(7));
map(&mut me, "8", PushCountDigit(8));
map(&mut me, "9", PushCountDigit(9));
map(&mut me, "af", CreateFileInSelectedDirectory);
map(&mut me, "ad", CreateDirectoryInSelectedDirectory);
map(&mut me, "e", EditSelectedEntry);
map(&mut me, "d", DeleteSelectedEntry);
map(&mut me, "gg", MoveToLineOr(MoveToFirst));
map(&mut me, "G", MoveToLineOr(MoveToLast));
map(&mut me, "h", CollapseSelectedOr(MoveToParent));
map(&mut me, "H", MoveOrTraverseUpParent);
map(&mut me, "if", CreateFileInSelectedDirectory);
map(&mut me, "id", CreateDirectoryInSelectedDirectory);
map(&mut me, "j", MoveDown);
map(&mut me, "J", MoveToNextSibling.with_fallback(MoveDown));
map(&mut me, "k", MoveUp);
map(&mut me, "K", MoveToPreviousSibling.with_fallback(MoveUp));
map(&mut me, "l", ExpandSelectedOr(MoveDown));
map(&mut me, "of", CreateFileInParentOfSelectedEntry);
map(&mut me, "od", CreateDirectoryInParentOfSelectedEntry);
map(&mut me, "q", Quit);
map(&mut me, "r", RenameSelectedEntry { prefill: true });
map(&mut me, "R", RenameSelectedEntry { prefill: false });
map(&mut me, "%", MoveBetweenFirstAndLastSibling);
map(&mut me, "<Ctrl-B>", MoveUp.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<Ctrl-C>", Quit);
map(&mut me, "<Ctrl-D>", MoveDown.with_default_count(ScreenHeightRatio(2)));
map(&mut me, "<Ctrl-F>", MoveDown.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<Ctrl-L>", RedrawScreen);
map(&mut me, "<Ctrl-N>", MoveDown);
map(&mut me, "<Ctrl-P>", MoveUp);
map(&mut me, "<Ctrl-U>", MoveUp.with_default_count(ScreenHeightRatio(2)));
map(&mut me, "<Space>", ExpandCollapse { default_depth: 1 });
map(&mut me, "<Ctrl-Space>", ExpandCollapse { default_depth: 1000 });
map(&mut me, "<Down>", MoveDown);
map(&mut me, "<Shift-Down>", MoveDown.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<Alt-Down>", MoveToNextSibling.with_fallback(MoveDown));
map(&mut me, "<Up>", MoveUp);
map(&mut me, "<Shift-Up>", MoveUp.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<Alt-Up>", MoveToPreviousSibling.with_fallback(MoveUp));
map(&mut me, "<Left>", CollapseSelectedOr(MoveToParent));
map(&mut me, "<Alt-Left>", MoveOrTraverseUpParent);
map(&mut me, "<Right>", ExpandSelectedOr(MoveDown));
map(&mut me, "<Del>", DeleteSelectedEntry);
map(&mut me, "<PageDown>", MoveDown.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<PageUp>", MoveUp.with_custom_count(ScreenHeightRatio(1)));
map(&mut me, "<F2>", RenameSelectedEntry { prefill: true });
map(&mut me, "<Shift-F2>", RenameSelectedEntry { prefill: false });
map(&mut me, "<F5>", RefreshChildrenOfSelected);
me
}
fn map(map: &mut ActionKeyMap, key_binding_str: &str, action: impl Action<FsLayer> + Sync + 'static) {
// Panic on error, since we are hard-coding the key bindings.
if let Err(err) = map.insert(key_binding_str, Box::new(action)) {
panic!("Failed to insert key binding '{}'. {}", err.sequence(), err.error());
}
}

View File

@@ -1,14 +1,15 @@
use slab_tree::NodeId; use slab_tree::NodeId;
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count, SimpleMovementAction}; use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTree;
use crate::state::Environment; use crate::state::Environment;
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M); pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> { impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_action_or_movement::<M, _>(layer, FsLayer::expand)) Some(perform_action_or_movement::<M, _>(layer, FsTree::expand))
} }
} }
@@ -16,16 +17,16 @@ pub struct CollapseSelectedOr<M: SimpleMovementAction>(pub M);
impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> { impl<M: SimpleMovementAction> MovementAction for CollapseSelectedOr<M> {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_action_or_movement::<M, _>(layer, FsLayer::collapse)) Some(perform_action_or_movement::<M, _>(layer, FsTree::collapse))
} }
} }
fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> bool { fn perform_action_or_movement<M: SimpleMovementAction, F>(layer: &mut FsLayer, action: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> bool {
perform_movement_with_count(layer, layer.registers.count, |layer, node_id| { perform_movement_with_count_from_register(layer, |tree, node_id| {
if action(layer, node_id) { if action(tree, node_id) {
Some(node_id) Some(node_id)
} else { } else {
get_simple_movement_target::<M>(layer, node_id) get_simple_movement_target::<M>(tree, node_id)
} }
}) })
} }

View File

@@ -1,15 +1,14 @@
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count, SimpleMovementAction}; use crate::component::filesystem::action::movement::{MovementAction, perform_movement_with_count_from_register, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
use crate::state::Environment; use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
pub struct MoveToNextSibling; pub struct MoveToNextSibling;
impl SimpleMovementAction for MoveToNextSibling { impl SimpleMovementAction for MoveToNextSibling {
fn get_target(_view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
selected_node.next_sibling_id() selected_node.next_sibling_id()
} }
} }
@@ -17,7 +16,7 @@ impl SimpleMovementAction for MoveToNextSibling {
pub struct MoveToPreviousSibling; pub struct MoveToPreviousSibling;
impl SimpleMovementAction for MoveToPreviousSibling { impl SimpleMovementAction for MoveToPreviousSibling {
fn get_target(_view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
selected_node.prev_sibling_id() selected_node.prev_sibling_id()
} }
} }
@@ -25,7 +24,7 @@ impl SimpleMovementAction for MoveToPreviousSibling {
pub struct MoveBetweenFirstAndLastSibling; pub struct MoveBetweenFirstAndLastSibling;
impl SimpleMovementAction for MoveBetweenFirstAndLastSibling { impl SimpleMovementAction for MoveBetweenFirstAndLastSibling {
fn get_target(_view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
if selected_node.next_sibling().is_none() { if selected_node.next_sibling().is_none() {
selected_node.parent().and_then(|node| node.first_child_id()) selected_node.parent().and_then(|node| node.first_child_id())
} else { } else {
@@ -37,7 +36,7 @@ impl SimpleMovementAction for MoveBetweenFirstAndLastSibling {
pub struct MoveToParent; pub struct MoveToParent;
impl SimpleMovementAction for MoveToParent { impl SimpleMovementAction for MoveToParent {
fn get_target(_view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
selected_node.parent_id() selected_node.parent_id()
} }
} }
@@ -46,19 +45,17 @@ pub struct MoveOrTraverseUpParent;
impl MovementAction for MoveOrTraverseUpParent { impl MovementAction for MoveOrTraverseUpParent {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_movement_with_count(layer, layer.registers.count, Self::get_target)) Some(perform_movement_with_count_from_register(layer, Self::get_target))
} }
} }
impl MoveOrTraverseUpParent { impl MoveOrTraverseUpParent {
fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> { fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
let view = &layer.tree.view; if let Some(node) = tree.get_view_node(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(&node);
if let Some(node) = view.get(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(view, &node);
if target_node_id.is_some() { if target_node_id.is_some() {
return target_node_id; return target_node_id;
} else if let Some(target_node_id) = layer.traverse_up_root() { } else if let Some(target_node_id) = tree.traverse_up_root() {
return Some(target_node_id) return Some(target_node_id)
} }
} }

View File

@@ -2,15 +2,15 @@ use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction}; use crate::component::filesystem::action::movement::{get_simple_movement_target, MovementAction, MoveToParent, perform_movement_with_count_from, SimpleMovementAction};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
use crate::state::Environment; use crate::state::Environment;
/// Moves up `count` lines (1 line by default). /// Moves up `count` lines (1 line by default).
pub struct MoveUp; pub struct MoveUp;
impl SimpleMovementAction for MoveUp { impl SimpleMovementAction for MoveUp {
fn get_target(tree: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
tree.get_node_above(selected_node) selected_node.above_id()
} }
} }
@@ -18,8 +18,8 @@ impl SimpleMovementAction for MoveUp {
pub struct MoveDown; pub struct MoveDown;
impl SimpleMovementAction for MoveDown { impl SimpleMovementAction for MoveDown {
fn get_target(tree: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> {
tree.get_node_below(selected_node) selected_node.below_id()
} }
} }
@@ -28,16 +28,15 @@ pub struct MoveToFirst;
impl MovementAction for MoveToFirst { impl MovementAction for MoveToFirst {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(Self::get_target(layer)) Some(Self::get_target(&mut layer.tree))
} }
} }
impl MoveToFirst { impl MoveToFirst {
fn get_target(layer: &mut FsLayer) -> NodeId where Self: Sized { fn get_target(tree: &mut FsTree) -> NodeId where Self: Sized {
let view = &layer.tree.view; let mut target_node_id = tree.selected_view_node_id;
let mut target_node_id = layer.selected_view_node_id;
while let Some(node_id) = view.get(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(view, &node)) { while let Some(node_id) = tree.get_view_node(target_node_id).and_then(|node| <MoveToParent as SimpleMovementAction>::get_target(&node)) {
target_node_id = node_id; target_node_id = node_id;
} }
@@ -50,9 +49,9 @@ pub struct MoveToLast;
impl MovementAction for MoveToLast { impl MovementAction for MoveToLast {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
let first_node_id = MoveToFirst::get_target(layer); let first_node_id = MoveToFirst::get_target(&mut layer.tree);
let last_node_id = layer.tree.view.get_last_descendant_or_self(first_node_id); let last_node_id = layer.tree.get_view_node(first_node_id).map(|node| node.last_descendant_or_self().node_id());
Some(last_node_id) last_node_id
} }
} }
@@ -63,9 +62,10 @@ pub struct MoveToLineOr<A: MovementAction>(pub A);
impl<A: MovementAction> MovementAction for MoveToLineOr<A> { impl<A: MovementAction> MovementAction for MoveToLineOr<A> {
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
if let Some(line_number) = layer.registers.count { if let Some(line_number) = layer.registers.count {
let tree = &mut layer.tree;
let line_index = Some(line_number.saturating_sub(1)); let line_index = Some(line_number.saturating_sub(1));
let first_node_id = MoveToFirst::get_target(layer); let first_node_id = MoveToFirst::get_target(tree);
Some(perform_movement_with_count_from(layer, line_index, first_node_id, get_simple_movement_target::<MoveDown>)) Some(perform_movement_with_count_from(tree, line_index, first_node_id, get_simple_movement_target::<MoveDown>))
} else { } else {
self.0.get_target(layer, environment) self.0.get_target(layer, environment)
} }

View File

@@ -1,7 +1,7 @@
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
@@ -25,7 +25,7 @@ pub trait MovementAction {
impl<T: MovementAction> Action<FsLayer> for T { impl<T: MovementAction> Action<FsLayer> for T {
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
if let Some(target_node_id) = self.get_target(layer, environment) { if let Some(target_node_id) = self.get_target(layer, environment) {
layer.selected_view_node_id = target_node_id; layer.tree.selected_view_node_id = target_node_id;
ActionResult::Draw ActionResult::Draw
} else { } else {
ActionResult::Nothing ActionResult::Nothing
@@ -33,15 +33,19 @@ impl<T: MovementAction> Action<FsLayer> for T {
} }
} }
fn perform_movement_with_count<F>(layer: &mut FsLayer, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> { fn perform_movement_with_count_from_register<F>(layer: &mut FsLayer, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target) perform_movement_with_count(&mut layer.tree, layer.registers.count, get_target)
} }
fn perform_movement_with_count_from<F>(layer: &mut FsLayer, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsLayer, NodeId) -> Option<NodeId> { fn perform_movement_with_count<F>(tree: &mut FsTree, count: Option<usize>, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
perform_movement_with_count_from(tree, count, tree.selected_view_node_id, get_target)
}
fn perform_movement_with_count_from<F>(tree: &mut FsTree, count: Option<usize>, start_node_id: NodeId, get_target: F) -> NodeId where F: Fn(&mut FsTree, NodeId) -> Option<NodeId> {
let mut target_node_id = start_node_id; let mut target_node_id = start_node_id;
for _ in 0..count.unwrap_or(1) { for _ in 0..count.unwrap_or(1) {
if let Some(node_id) = get_target(layer, target_node_id) { if let Some(node_id) = get_target(tree, target_node_id) {
target_node_id = node_id; target_node_id = node_id;
} else { } else {
break; break;
@@ -52,16 +56,15 @@ fn perform_movement_with_count_from<F>(layer: &mut FsLayer, count: Option<usize>
} }
pub trait SimpleMovementAction { pub trait SimpleMovementAction {
fn get_target(view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized; fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized;
} }
impl<T: SimpleMovementAction> MovementAction for T { impl<T: SimpleMovementAction> MovementAction for T {
fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, _environment: &Environment) -> Option<NodeId> where Self: Sized {
Some(perform_movement_with_count(layer, layer.registers.count, get_simple_movement_target::<T>)) Some(perform_movement_with_count_from_register(layer, get_simple_movement_target::<T>))
} }
} }
fn get_simple_movement_target<T: SimpleMovementAction>(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> { fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
let view = &layer.tree.view; tree.get_view_node(node_id).and_then(|node| T::get_target(&node))
view.get(node_id).and_then(|node| T::get_target(view, &node))
} }

View File

@@ -37,7 +37,7 @@ impl<A: SimpleMovementAction + 'static, C: MovementCount> MovementWithCountFacto
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> { impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized { fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
let count = self.0.get_count(layer.registers.count, environment); let count = self.0.get_count(layer.registers.count, environment);
Some(perform_movement_with_count(layer, Some(count), get_simple_movement_target::<A>)) Some(perform_movement_with_count(&mut layer.tree, Some(count), get_simple_movement_target::<A>))
} }
} }

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::action::movement::SimpleMovementAction; use crate::component::filesystem::action::movement::SimpleMovementAction;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::FsTreeViewNode;
/// A movement action with fallback. /// A movement action with fallback.
pub struct MovementWithFallback<A: SimpleMovementAction, F: SimpleMovementAction>(PhantomData<A>, PhantomData<F>); pub struct MovementWithFallback<A: SimpleMovementAction, F: SimpleMovementAction>(PhantomData<A>, PhantomData<F>);
@@ -21,7 +21,7 @@ impl<A: SimpleMovementAction, F: SimpleMovementAction> MovementWithFallbackFacto
} }
impl<A: SimpleMovementAction, F: SimpleMovementAction> SimpleMovementAction for MovementWithFallback<A, F> { impl<A: SimpleMovementAction, F: SimpleMovementAction> SimpleMovementAction for MovementWithFallback<A, F> {
fn get_target(view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized { fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
A::get_target(view, selected_node).or_else(|| F::get_target(view, selected_node)) A::get_target(selected_node).or_else(|| F::get_target(selected_node))
} }
} }

View File

@@ -5,6 +5,7 @@ use slab_tree::NodeId;
use crate::component::dialog::message::MessageDialogLayer; use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTree;
use crate::state::action::{Action, ActionResult}; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
@@ -19,19 +20,19 @@ impl Action<FsLayer> for ExpandCollapse {
return ActionResult::Nothing; return ActionResult::Nothing;
} }
if layer.expand_or_collapse(layer.selected_view_node_id) { if layer.tree.expand_or_collapse(layer.tree.selected_view_node_id) {
if depth > 1 { if depth > 1 {
if let Some(node) = layer.selected_node() { if let Some(node) = layer.tree.selected_node() {
if node.data().is_expanded() { if node.data().is_expanded() {
let child_node_ids = node.children().map(|node| node.node_id()).collect(); let child_node_ids = node.children().map(|node| node.node_id()).collect();
let remaining_depth = depth.saturating_sub(1); let remaining_depth = depth.saturating_sub(1);
if !expand_children_to_depth(layer, child_node_ids, remaining_depth) { if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::build() return ActionResult::push_layer(MessageDialogLayer::build()
.y(layer.dialog_y()) .y(layer.dialog_y())
.color(Color::LightYellow) .color(Color::LightYellow)
.title("Expansion Stopped") .title("Expansion Stopped")
.message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs())) .message(format!("Expansion was taking more than {} seconds, stopping now.", MAX_EXPANSION_TIME.as_secs()))
.ok())); .ok());
} }
} }
} }
@@ -46,7 +47,7 @@ impl Action<FsLayer> for ExpandCollapse {
const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10); const MAX_EXPANSION_TIME: Duration = Duration::from_secs(10);
fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool { fn expand_children_to_depth(tree: &mut FsTree, mut child_node_ids: Vec<NodeId>, max_depth: usize) -> bool {
let start_time = Instant::now(); let start_time = Instant::now();
let mut current_pass_node_ids = Vec::new(); let mut current_pass_node_ids = Vec::new();
@@ -55,8 +56,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
for node_id in &current_pass_node_ids { for node_id in &current_pass_node_ids {
let node_id = *node_id; let node_id = *node_id;
layer.tree.expand(node_id); tree.expand(node_id);
get_child_node_ids(layer, node_id, &mut child_node_ids); get_child_node_ids(tree, node_id, &mut child_node_ids);
if start_time.elapsed() >= MAX_EXPANSION_TIME { if start_time.elapsed() >= MAX_EXPANSION_TIME {
return false; return false;
@@ -67,8 +68,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
true true
} }
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) { fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
if let Some(node) = layer.tree.view.get(node_id) { if let Some(node) = tree.get_view_node(node_id) {
for child in node.children() { for child in node.children() {
output_node_ids.push(child.node_id()); output_node_ids.push(child.node_id());
} }

View File

@@ -6,7 +6,7 @@ pub struct RefreshChildrenOfSelected;
impl Action<FsLayer> for RefreshChildrenOfSelected { impl Action<FsLayer> for RefreshChildrenOfSelected {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult { fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
if layer.refresh_children(layer.selected_view_node_id) { if layer.tree.refresh_children(layer.tree.selected_view_node_id) {
ActionResult::Draw ActionResult::Draw
} else { } else {
ActionResult::Nothing ActionResult::Nothing

View File

@@ -0,0 +1 @@
mod shell;

View File

@@ -0,0 +1 @@
pub struct RunShellCommand;

View File

@@ -0,0 +1,98 @@
use lazy_static::lazy_static;
use crate::component::filesystem::{ActionKeyMap, FsLayer};
use crate::component::filesystem::action::application::{EnterCommandMode, Quit, RedrawScreen};
use crate::component::filesystem::action::count::PushCountDigit;
use crate::component::filesystem::action::file::{CreateDirectoryInParentOfSelectedEntry, CreateDirectoryInSelectedDirectory, CreateFileInParentOfSelectedEntry, CreateFileInSelectedDirectory, DeleteSelectedEntry, EditSelectedEntry, RenameSelectedEntry};
use crate::component::filesystem::action::movement::{CollapseSelectedOr, ExpandSelectedOr, MoveBetweenFirstAndLastSibling, MoveDown, MovementWithCountFactory, MovementWithFallbackFactory, MoveOrTraverseUpParent, MoveToFirst, MoveToLast, MoveToLineOr, MoveToNextSibling, MoveToParent, MoveToPreviousSibling, MoveUp, ScreenHeightRatio};
use crate::component::filesystem::action::tree::{ExpandCollapse, RefreshChildrenOfSelected};
use crate::input::keymap::KeyMapInsertError;
use crate::state::action::Action;
lazy_static! {
pub static ref ACTION_MAP: Result<ActionKeyMap, KeyMapInsertError> = create_action_map();
}
pub fn get_action_map() -> Result<&'static ActionKeyMap, &'static KeyMapInsertError> {
return ACTION_MAP.as_ref();
}
fn create_action_map() -> Result<ActionKeyMap, KeyMapInsertError> {
let mut me = ActionKeyMap::new();
map(&mut me, "0", PushCountDigit(0))?;
map(&mut me, "1", PushCountDigit(1))?;
map(&mut me, "2", PushCountDigit(2))?;
map(&mut me, "3", PushCountDigit(3))?;
map(&mut me, "4", PushCountDigit(4))?;
map(&mut me, "5", PushCountDigit(5))?;
map(&mut me, "6", PushCountDigit(6))?;
map(&mut me, "7", PushCountDigit(7))?;
map(&mut me, "8", PushCountDigit(8))?;
map(&mut me, "9", PushCountDigit(9))?;
map(&mut me, "af", CreateFileInSelectedDirectory)?;
map(&mut me, "ad", CreateDirectoryInSelectedDirectory)?;
map(&mut me, "e", EditSelectedEntry)?;
map(&mut me, "d", DeleteSelectedEntry)?;
map(&mut me, "gg", MoveToLineOr(MoveToFirst))?;
map(&mut me, "G", MoveToLineOr(MoveToLast))?;
map(&mut me, "h", CollapseSelectedOr(MoveToParent))?;
map(&mut me, "H", MoveOrTraverseUpParent)?;
map(&mut me, "if", CreateFileInSelectedDirectory)?;
map(&mut me, "id", CreateDirectoryInSelectedDirectory)?;
map(&mut me, "j", MoveDown)?;
map(&mut me, "J", MoveToNextSibling.with_fallback(MoveDown))?;
map(&mut me, "k", MoveUp)?;
map(&mut me, "K", MoveToPreviousSibling.with_fallback(MoveUp))?;
map(&mut me, "l", ExpandSelectedOr(MoveDown))?;
map(&mut me, "of", CreateFileInParentOfSelectedEntry)?;
map(&mut me, "od", CreateDirectoryInParentOfSelectedEntry)?;
map(&mut me, "q", Quit)?;
map(&mut me, "r", RenameSelectedEntry { prefill: true })?;
map(&mut me, "R", RenameSelectedEntry { prefill: false })?;
map(&mut me, "%", MoveBetweenFirstAndLastSibling)?;
map(&mut me, ":", EnterCommandMode)?;
map(&mut me, "<Ctrl-B>", MoveUp.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<Ctrl-C>", Quit)?;
map(&mut me, "<Ctrl-D>", MoveDown.with_default_count(ScreenHeightRatio(2)))?;
map(&mut me, "<Ctrl-F>", MoveDown.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<Ctrl-L>", RedrawScreen)?;
map(&mut me, "<Ctrl-N>", MoveDown)?;
map(&mut me, "<Ctrl-P>", MoveUp)?;
map(&mut me, "<Ctrl-U>", MoveUp.with_default_count(ScreenHeightRatio(2)))?;
map(&mut me, "<Space>", ExpandCollapse { default_depth: 1 })?;
map(&mut me, "<Ctrl-Space>", ExpandCollapse { default_depth: 1000 })?;
map(&mut me, "<Down>", MoveDown)?;
map(&mut me, "<Shift-Down>", MoveDown.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<Alt-Down>", MoveToNextSibling.with_fallback(MoveDown))?;
map(&mut me, "<Up>", MoveUp)?;
map(&mut me, "<Shift-Up>", MoveUp.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<Alt-Up>", MoveToPreviousSibling.with_fallback(MoveUp))?;
map(&mut me, "<Left>", CollapseSelectedOr(MoveToParent))?;
map(&mut me, "<Alt-Left>", MoveOrTraverseUpParent)?;
map(&mut me, "<Right>", ExpandSelectedOr(MoveDown))?;
map(&mut me, "<Del>", DeleteSelectedEntry)?;
map(&mut me, "<PageDown>", MoveDown.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<PageUp>", MoveUp.with_custom_count(ScreenHeightRatio(1)))?;
map(&mut me, "<F2>", RenameSelectedEntry { prefill: true })?;
map(&mut me, "<Shift-F2>", RenameSelectedEntry { prefill: false })?;
map(&mut me, "<F5>", RefreshChildrenOfSelected)?;
Ok(me)
}
fn map(map: &mut ActionKeyMap, key_binding_str: &str, action: impl Action<FsLayer> + Sync + 'static) -> Result<(), KeyMapInsertError> {
map.insert(key_binding_str, Box::new(action))
}

View File

@@ -1,62 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use slab_tree::NodeId;
use crate::component::filesystem::FsLayer;
pub type FsLayerPendingEvents = Rc<RefCell<Vec<FsLayerEvent>>>;
pub enum FsLayerEvent {
RefreshViewNodeChildren(NodeId),
SelectViewNodeChildByFileName(NodeId, String),
DeleteViewNode(NodeId),
}
impl FsLayerEvent {
pub fn enqueue(self, pending_events: &FsLayerPendingEvents) -> bool {
if let Ok(mut pending_events) = pending_events.try_borrow_mut() {
pending_events.push(self);
true
} else {
false
}
}
pub fn handle(self, layer: &mut FsLayer) {
match self {
Self::RefreshViewNodeChildren(view_node_id) => handle_refresh_view_node_children(layer, view_node_id),
Self::SelectViewNodeChildByFileName(parent_view_node_id, child_file_name) => handle_select_view_node_child_by_name(layer, parent_view_node_id, child_file_name.as_str()),
Self::DeleteViewNode(view_node_id) => handle_delete_view_node(layer, view_node_id),
}
}
}
fn handle_refresh_view_node_children(layer: &mut FsLayer, view_node_id: NodeId) {
layer.refresh_children(view_node_id);
}
fn handle_select_view_node_child_by_name(layer: &mut FsLayer, parent_view_node_id: NodeId, child_file_name: &str) {
layer.tree.expand(parent_view_node_id);
if let Some(parent_node) = layer.tree.view.get(parent_view_node_id) {
for child_node in parent_node.children() {
if layer.tree.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
layer.selected_view_node_id = child_node.node_id();
return;
}
}
}
}
fn handle_delete_view_node(layer: &mut FsLayer, view_node_id: NodeId) {
let view = &mut layer.tree.view;
if layer.selected_view_node_id == view_node_id {
layer.selected_view_node_id = view.get_node_below_id(view_node_id).or_else(|| view.get_node_above_id(view_node_id)).unwrap_or_else(|| view.root_id());
}
if let Some(view_node) = view.remove(view_node_id) {
layer.tree.model.remove(view_node.model_node_id());
}
}

View File

@@ -1,112 +1,65 @@
use std::cell::RefCell;
use std::path::Path; use std::path::Path;
use std::rc::Rc;
use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::event::FsLayerPendingEvents;
use crate::component::filesystem::registers::FsTreeRegisters; use crate::component::filesystem::registers::FsTreeRegisters;
use crate::component::filesystem::tree::{FsTree, FsTreeViewNode}; use crate::component::filesystem::tree::FsTree;
use crate::file::FileOwnerNameCache; use crate::file::FileOwnerNameCache;
use crate::input::keymap::{KeyBinding, KeyMapLookupResult}; use crate::input::keymap::{KeyBinding, KeyMap, KeyMapLookupResult};
use crate::state::action::ActionResult; use crate::state::action::{Action, ActionResult};
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::{EventQueue, EventResult};
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
mod action; mod action;
mod event; mod command;
mod registers;
mod render; mod render;
mod tree; mod tree;
mod registers; pub mod defaults;
pub type ActionKeyMap = KeyMap<Box<dyn Action<FsLayer> + Sync>>;
pub struct FsLayer { pub struct FsLayer {
action_map: &'static ActionKeyMap,
pub tree: FsTree, pub tree: FsTree,
pub selected_view_node_id: NodeId, tree_structure_version: u32,
pub registers: FsTreeRegisters,
cursor_y: u16, cursor_y: u16,
pub registers: FsTreeRegisters,
pending_keys: Vec<KeyBinding>, pending_keys: Vec<KeyBinding>,
pending_events: FsLayerPendingEvents, event_queue: EventQueue<FsLayer>,
file_owner_name_cache: FileOwnerNameCache, file_owner_name_cache: FileOwnerNameCache,
column_width_cache: Option<ColumnWidths>, column_width_cache: Option<ColumnWidths>,
} }
impl FsLayer { impl FsLayer {
pub fn with_root_path(root_path: &Path) -> Self { pub fn new(root_path: &Path, action_map: &'static ActionKeyMap) -> Self {
// Initialize action map early in case it errors.
let _ = *action::ACTION_MAP;
let mut tree = FsTree::with_root_path(root_path);
let root_id = tree.view.root_id();
tree.expand(root_id);
Self { Self {
tree, action_map,
selected_view_node_id: root_id, tree: FsTree::with_root_path(root_path),
tree_structure_version: 0,
cursor_y: 0, cursor_y: 0,
registers: FsTreeRegisters::new(), registers: FsTreeRegisters::new(),
pending_keys: Vec::new(), pending_keys: Vec::new(),
pending_events: Rc::new(RefCell::new(Vec::new())), event_queue: EventQueue::new(),
file_owner_name_cache: FileOwnerNameCache::new(), file_owner_name_cache: FileOwnerNameCache::new(),
column_width_cache: None, column_width_cache: None,
} }
} }
pub fn events(&self) -> EventQueue<Self> {
self.event_queue.rc_clone()
}
pub const fn dialog_y(&self) -> u16 { pub const fn dialog_y(&self) -> u16 {
self.cursor_y.saturating_add(1) self.cursor_y.saturating_add(1)
} }
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
return self.tree.view.get(self.selected_view_node_id);
}
pub fn expand(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.expand(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.collapse(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.expand_or_collapse(view_node_id);
tree_structure_changed_if_true(self, result)
}
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
let result = self.tree.refresh_children(view_node_id);
if result && self.selected_node().is_none() {
self.selected_view_node_id = view_node_id;
}
tree_structure_changed_if_true(self, result)
}
pub fn traverse_up_root(&mut self) -> Option<NodeId> {
let new_root_id = self.tree.traverse_up_root();
tree_structure_changed_if_true(self, new_root_id.is_some());
new_root_id
}
}
fn tree_structure_changed(layer: &mut FsLayer) {
layer.column_width_cache.take();
}
fn tree_structure_changed_if_true(layer: &mut FsLayer, result: bool) -> bool {
if result {
tree_structure_changed(layer);
}
result
} }
impl Layer for FsLayer { impl Layer for FsLayer {
fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult { fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult {
self.pending_keys.push(key_binding); self.pending_keys.push(key_binding);
match action::ACTION_MAP.lookup(&self.pending_keys) { match self.action_map.lookup(&self.pending_keys) {
KeyMapLookupResult::Prefix => { KeyMapLookupResult::Prefix => {
ActionResult::Nothing ActionResult::Nothing
} }
@@ -133,9 +86,14 @@ impl Layer for FsLayer {
} }
} }
fn handle_events(&mut self, environment: &Environment) -> EventResult {
self.event_queue.take().into_iter().fold(EventResult::Nothing, |result, event| result.merge(event.dispatch(self, environment)))
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
for event in self.pending_events.take() { if self.tree_structure_version != self.tree.structure_version() {
event.handle(self); self.tree_structure_version = self.tree.structure_version();
self.column_width_cache.take();
} }
render::render(self, frame); render::render(self, frame);

View File

@@ -7,7 +7,7 @@ use ratatui::widgets::{Clear, Widget};
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::{ColumnWidths, FsLayer}; use crate::component::filesystem::{ColumnWidths, FsLayer};
use crate::component::filesystem::tree::{FsTree, FsTreeView, FsTreeViewNode}; use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
use crate::file::{FileEntry, FileKind, FileOwnerNameCache}; use crate::file::{FileEntry, FileKind, FileOwnerNameCache};
use crate::state::view::Frame; use crate::state::view::Frame;
@@ -24,7 +24,7 @@ pub fn render(layer: &mut FsLayer, frame: &mut Frame) {
let column_widths = get_or_update_column_widths(layer, size.width); let column_widths = get_or_update_column_widths(layer, size.width);
let file_owner_name_cache = &mut layer.file_owner_name_cache; let file_owner_name_cache = &mut layer.file_owner_name_cache;
let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.selected_view_node_id, size.height as usize); let (rows, cursor_y) = collect_displayed_rows(&layer.tree, layer.tree.selected_view_node_id, size.height as usize);
layer.cursor_y = cursor_y; layer.cursor_y = cursor_y;
frame.render_widget(Clear, size); frame.render_widget(Clear, size);
@@ -37,7 +37,7 @@ fn get_or_update_column_widths(layer: &mut FsLayer, cols: u16) -> ColumnWidths {
let mut user: usize = 0; let mut user: usize = 0;
let mut group: usize = 0; let mut group: usize = 0;
for node in &layer.tree.view { for node in layer.tree.view_iter() {
let entry = layer.tree.get_entry(&node).unwrap_or_else(|| FileEntry::dummy_as_ref()); let entry = layer.tree.get_entry(&node).unwrap_or_else(|| FileEntry::dummy_as_ref());
name = max(name, get_node_level(&node).saturating_add(Span::from(entry.name().str()).width())); name = max(name, get_node_level(&node).saturating_add(Span::from(entry.name().str()).width()));
@@ -63,7 +63,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
let mut displayed_rows = Vec::with_capacity(terminal_rows); let mut displayed_rows = Vec::with_capacity(terminal_rows);
let mut cursor_y: u16 = 0; let mut cursor_y: u16 = 0;
if let Some(middle_node) = tree.view.get(selected_node_id).or_else(|| tree.view.root()) { if let Some(middle_node) = tree.selected_node().or_else(|| tree.view_root_node()) {
let middle_node_id = middle_node.node_id(); let middle_node_id = middle_node.node_id();
displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id)); displayed_rows.push(NodeRow::from(&middle_node, tree, middle_node_id == selected_node_id));
@@ -72,7 +72,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
let mut cursor_down_id = Some(middle_node_id); let mut cursor_down_id = Some(middle_node_id);
while displayed_rows.len() < terminal_rows { while displayed_rows.len() < terminal_rows {
if let Some(next_node_up) = move_cursor(tree, &mut cursor_up_id, FsTreeView::get_node_above) { if let Some(next_node_up) = move_cursor(tree, &mut cursor_up_id, |node| node.above_id()) {
displayed_rows.insert(0, NodeRow::from(&next_node_up, tree, false)); displayed_rows.insert(0, NodeRow::from(&next_node_up, tree, false));
cursor_y = cursor_y.saturating_add(1); cursor_y = cursor_y.saturating_add(1);
} }
@@ -81,7 +81,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
break; break;
} }
if let Some(next_node_down) = move_cursor(tree, &mut cursor_down_id, FsTreeView::get_node_below) { if let Some(next_node_down) = move_cursor(tree, &mut cursor_down_id, |node| node.below_id()) {
displayed_rows.push(NodeRow::from(&next_node_down, tree, false)); displayed_rows.push(NodeRow::from(&next_node_down, tree, false));
} }
@@ -94,12 +94,11 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
(displayed_rows, cursor_y) (displayed_rows, cursor_y)
} }
fn move_cursor<'a, F>(tree: &'a FsTree, cursor: &mut Option<NodeId>, func: F) -> Option<NodeRef<'a, FsTreeViewNode>> where F: FnOnce(&FsTreeView, &NodeRef<FsTreeViewNode>) -> Option<NodeId> { fn move_cursor<'a, F>(tree: &'a FsTree, cursor: &mut Option<NodeId>, func: F) -> Option<NodeRef<'a, FsTreeViewNode>> where F: FnOnce(NodeRef<FsTreeViewNode>) -> Option<NodeId> {
let view = &tree.view;
let next_node = cursor let next_node = cursor
.and_then(|id| view.get(id)) .and_then(|id| tree.get_view_node(id))
.and_then(|node| func(view, &node)) .and_then(func)
.and_then(|id| view.get(id)); .and_then(|id| tree.get_view_node(id));
*cursor = next_node.as_ref().map(NodeRef::node_id); *cursor = next_node.as_ref().map(NodeRef::node_id);

View File

@@ -2,6 +2,7 @@ use std::path::Path;
use slab_tree::{NodeId, NodeRef}; use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::tree::view::FsTreeViewIterator;
use crate::file::FileEntry; use crate::file::FileEntry;
pub use self::model::FsTreeModel; pub use self::model::FsTreeModel;
@@ -13,16 +14,47 @@ mod model;
mod view; mod view;
pub struct FsTree { pub struct FsTree {
pub model: FsTreeModel, model: FsTreeModel,
pub view: FsTreeView, view: FsTreeView,
pub selected_view_node_id: NodeId,
structure_version: u32,
} }
impl FsTree { impl FsTree {
pub fn with_root_path(path: &Path) -> Self { pub fn with_root_path(path: &Path) -> Self {
let model = FsTreeModel::with_root_path(path); let model = FsTreeModel::with_root_path(path);
let view = FsTreeView::from_model_root(&model); let view = FsTreeView::from_model_root(&model);
let root_id = view.root_id();
Self { model, view } let mut tree = Self {
model,
view,
selected_view_node_id: root_id,
structure_version: 0,
};
tree.expand(root_id);
tree
}
pub const fn structure_version(&self) -> u32 {
self.structure_version
}
pub fn selected_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
return self.view.get(self.selected_view_node_id);
}
pub fn view_root_node(&self) -> Option<NodeRef<FsTreeViewNode>> {
self.view.root()
}
pub fn view_iter(&self) -> FsTreeViewIterator {
self.view.into_iter()
}
pub fn get_view_node(&self, view_node_id: NodeId) -> Option<NodeRef<FsTreeViewNode>> {
self.view.get(view_node_id)
} }
pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> { pub fn get_entry(&self, node: &NodeRef<FsTreeViewNode>) -> Option<&FileEntry> {
@@ -32,26 +64,79 @@ impl FsTree {
} }
pub fn expand(&mut self, view_node_id: NodeId) -> bool { pub fn expand(&mut self, view_node_id: NodeId) -> bool {
self.view.expand(view_node_id, &mut self.model) let result = self.view.expand(view_node_id, &mut self.model);
self.structure_changed_if_true(result)
} }
pub fn collapse(&mut self, view_node_id: NodeId) -> bool { pub fn collapse(&mut self, view_node_id: NodeId) -> bool {
self.view.collapse(view_node_id) let result = self.view.collapse(view_node_id);
self.structure_changed_if_true(result)
} }
pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool { pub fn expand_or_collapse(&mut self, view_node_id: NodeId) -> bool {
self.view.expand_or_collapse(view_node_id, &mut self.model) let result = self.view.expand_or_collapse(view_node_id, &mut self.model);
self.structure_changed_if_true(result)
} }
pub fn traverse_up_root(&mut self) -> Option<NodeId> { pub fn traverse_up_root(&mut self) -> Option<NodeId> {
self.view.traverse_up_root(&mut self.model) let new_root_id = self.view.traverse_up_root(&mut self.model);
self.structure_changed_if(new_root_id, Option::is_some)
} }
pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool { pub fn refresh_children(&mut self, view_node_id: NodeId) -> bool {
if let Some(view_node) = self.view.get(view_node_id) { if let Some(view_node) = self.view.get(view_node_id) {
self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model) let result = self.model.refresh_children(view_node.data().model_node_id()) && self.view.refresh_children(view_node_id, &self.model);
if result && self.selected_node().is_none() {
self.selected_view_node_id = view_node_id;
}
self.structure_changed_if_true(result)
} else { } else {
false false
} }
} }
pub fn select_child_node_by_name(&mut self, parent_view_node_id: NodeId, child_file_name: &str) -> bool {
self.expand(parent_view_node_id);
if let Some(parent_node) = self.view.get(parent_view_node_id) {
for child_node in parent_node.children() {
if self.get_entry(&child_node).is_some_and(|entry| entry.name().str() == child_file_name) {
self.selected_view_node_id = child_node.node_id();
return true;
}
}
}
false
}
pub fn delete_node(&mut self, view_node_id: NodeId) -> bool {
let view = &mut self.view;
if self.selected_view_node_id == view_node_id {
self.selected_view_node_id = view.get(view_node_id).and_then(|node| node.below_id().or_else(|| node.above_id())).unwrap_or_else(|| view.root_id());
}
if let Some(view_node) = view.remove(view_node_id) {
self.model.remove(view_node.model_node_id());
true
} else {
false
}
}
fn structure_changed(&mut self) {
self.structure_version = self.structure_version.wrapping_add(1);
}
fn structure_changed_if<T, F>(&mut self, result: T, predicate: F) -> T where F: FnOnce(&T) -> bool {
if predicate(&result) {
self.structure_changed();
}
result
}
fn structure_changed_if_true(&mut self, result: bool) -> bool {
self.structure_changed_if(result, |result| *result)
}
} }

View File

@@ -5,7 +5,6 @@ use slab_tree::NodeId;
use crate::component::filesystem::tree::{FsTreeModel, FsTreeModelNode}; use crate::component::filesystem::tree::{FsTreeModel, FsTreeModelNode};
use crate::file::FileEntry; use crate::file::FileEntry;
use crate::util::slab_tree::{NodeMutExtensions, NodeRefExtensions};
impl FsTreeModel { impl FsTreeModel {
pub fn resolve_children(&mut self, node_id: NodeId) -> Option<Vec<NodeId>> { pub fn resolve_children(&mut self, node_id: NodeId) -> Option<Vec<NodeId>> {

View File

@@ -1,48 +0,0 @@
use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
use crate::util::slab_tree::NodeRefExtensions;
impl FsTreeView {
pub fn get_node_above(&self, node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> {
if let Some(prev_sibling_id) = node.prev_sibling_id() {
Some(self.get_last_descendant_or_self(prev_sibling_id))
} else {
node.parent_id()
}
}
#[allow(clippy::unused_self)]
pub fn get_node_below(&self, node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> {
if let Some(next_id) = node.first_child_id() {
Some(next_id)
} else if let Some(next_id) = node.next_sibling_id() {
Some(next_id)
} else {
for ancestor in node.ancestors() {
if let Some(next_id) = ancestor.next_sibling_id() {
return Some(next_id);
}
}
None
}
}
pub fn get_node_above_id(&self, node_id: NodeId) -> Option<NodeId> {
self.get(node_id).and_then(|node| self.get_node_above(&node))
}
pub fn get_node_below_id(&self, node_id: NodeId) -> Option<NodeId> {
self.get(node_id).and_then(|node| self.get_node_below(&node))
}
pub fn get_last_descendant_or_self(&self, id: NodeId) -> NodeId {
let mut id = id;
while let Some(node_id) = self.get(id).and_then(|node| node.last_child_id()) {
id = node_id;
}
id
}
}

View File

@@ -2,7 +2,8 @@ use slab_tree::{NodeId, NodeMut, NodeRef, RemoveBehavior, Tree};
use crate::component::filesystem::tree::FsTreeModel; use crate::component::filesystem::tree::FsTreeModel;
mod above_below; pub use self::iterator::FsTreeViewIterator;
mod expand_collapse; mod expand_collapse;
mod iterator; mod iterator;
mod refresh; mod refresh;

View File

@@ -4,13 +4,13 @@ use crate::component::filesystem::tree::{FsTreeModel, FsTreeView, FsTreeViewNode
impl FsTreeView { impl FsTreeView {
pub fn traverse_up_root(&mut self, model: &mut FsTreeModel) -> Option<NodeId> { pub fn traverse_up_root(&mut self, model: &mut FsTreeModel) -> Option<NodeId> {
let old_moodel_root_id = model.root_id(); let old_model_root_id = model.root_id();
if let Some(new_model_root_id) = model.traverse_up_root() { if let Some(new_model_root_id) = model.traverse_up_root() {
self.set_root(new_model_root_id); self.set_root(new_model_root_id);
if let Some(mut new_view_root) = self.get_mut(self.root_id) { if let Some(mut new_view_root) = self.get_mut(self.root_id) {
Self::resolve_new_root_children(&mut new_view_root, model, old_moodel_root_id, new_model_root_id); Self::resolve_new_root_children(&mut new_view_root, model, old_model_root_id, new_model_root_id);
Some(self.root_id) Some(self.root_id)
} else { } else {
None None

View File

@@ -1,12 +1,16 @@
use std::cmp::min;
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::layout::Rect; use ratatui::layout::Rect;
use ratatui::style::{Color, Style}; use ratatui::style::{Color, Style};
use ratatui::text::Span;
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use crate::component::input::InputField; use crate::component::input::InputField;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@@ -17,12 +21,10 @@ pub struct InputFieldOverlayLayer<'a> {
} }
impl<'a> InputFieldOverlayLayer<'a> { impl<'a> InputFieldOverlayLayer<'a> {
pub fn new(read_only_prefix: &'a str, confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self { pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
Self { let field = InputField::new();
field: InputField::new(), let confirm_action = Box::new(confirm_action);
read_only_prefix, Self { field, read_only_prefix, confirm_action }
confirm_action,
}
} }
} }
@@ -53,6 +55,10 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
} }
} }
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) { fn render(&mut self, frame: &mut Frame) {
let size = frame.size(); let size = frame.size();
if size.width < 1 || size.height < 1 { if size.width < 1 || size.height < 1 {
@@ -62,6 +68,10 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
let x = size.x; let x = size.x;
let y = size.bottom().saturating_sub(1); let y = size.bottom().saturating_sub(1);
let prefix_text = Span::from(self.read_only_prefix);
let prefix_width = min(u16::try_from(prefix_text.width()).unwrap_or(u16::MAX), size.width.saturating_sub(2));
if prefix_width > 0 {
let prefix_style = Style::new() let prefix_style = Style::new()
.fg(Color::Black) .fg(Color::Black)
.bg(Color::LightYellow); .bg(Color::LightYellow);
@@ -69,10 +79,11 @@ impl<'a> Layer for InputFieldOverlayLayer<'a> {
let prefix_paragraph = Paragraph::new(self.read_only_prefix) let prefix_paragraph = Paragraph::new(self.read_only_prefix)
.style(prefix_style); .style(prefix_style);
frame.render_widget(prefix_paragraph, Rect { x, y, width: 1, height: 1 }); frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
}
if size.width > 1 { if size.width > prefix_width {
self.field.render(frame, x.saturating_add(1), y, size.width.saturating_sub(1), Color::LightYellow, Color::Yellow); self.field.render(frame, x.saturating_add(prefix_width), y, size.width.saturating_sub(prefix_width), Color::LightYellow, Color::Yellow);
} }
} }
} }

View File

@@ -50,11 +50,13 @@
#![allow(clippy::redundant_else)] #![allow(clippy::redundant_else)]
use std::env; use std::env;
use std::error::Error;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::ExitCode; use std::process::ExitCode;
use crate::state::init::StateInitializer;
use crate::state::view::View;
mod app; mod app;
mod component; mod component;
mod file; mod file;
@@ -65,11 +67,11 @@ mod util;
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
fn main() -> Result<ExitCode, Box<dyn Error>> { fn main() -> ExitCode {
let args = env::args_os().skip(1).collect::<Vec<_>>(); let args = env::args_os().skip(1).collect::<Vec<_>>();
if args.len() == 1 && args.get(0).is_some_and(|arg| arg == "-v" || arg == "--version") { if args.len() == 1 && args.get(0).is_some_and(|arg| arg == "-v" || arg == "--version") {
println!("{}", VERSION.unwrap_or("unknown")); println!("{}", VERSION.unwrap_or("unknown"));
return Ok(ExitCode::SUCCESS); return ExitCode::SUCCESS;
} }
#[allow(clippy::indexing_slicing)] // Guarded by condition. #[allow(clippy::indexing_slicing)] // Guarded by condition.
@@ -81,21 +83,20 @@ fn main() -> Result<ExitCode, Box<dyn Error>> {
if args.len() > 1 { if args.len() > 1 {
println!("Too many arguments!"); println!("Too many arguments!");
return Ok(ExitCode::SUCCESS); return ExitCode::FAILURE;
} }
match get_start_path(args.get(0)) { match get_start_path(args.get(0)) {
StartPathResult::Ok(path) => { StartPathResult::Ok(path) => {
app::run(&path)?; prepare_and_run_app(&path)
Ok(ExitCode::SUCCESS)
}, },
StartPathResult::InvalidPathArgument(path) => { StartPathResult::InvalidPathArgument(path) => {
println!("Invalid path: {}", path.to_string_lossy()); println!("Invalid path: {}", path.to_string_lossy());
Ok(ExitCode::FAILURE) ExitCode::FAILURE
}, },
StartPathResult::InvalidWorkingDirectory => { StartPathResult::InvalidWorkingDirectory => {
println!("Invalid working directory!"); println!("Invalid working directory!");
Ok(ExitCode::FAILURE) ExitCode::FAILURE
} }
} }
} }
@@ -119,3 +120,43 @@ fn get_start_path(path_arg: Option<&OsString>) -> StartPathResult {
StartPathResult::InvalidWorkingDirectory StartPathResult::InvalidWorkingDirectory
} }
} }
#[allow(clippy::print_stdout)]
fn prepare_and_run_app(start_path: &Path) -> ExitCode {
match component::filesystem::defaults::get_action_map() {
Ok(action_map) => {
run_app(&StateInitializer {
filesystem_start_path: start_path,
filesystem_action_map: action_map,
})
},
Err(e) => {
println!("Failed to initialize action map, could not insert key sequence: '{}'\nReason: {}", e.sequence(), e.error());
ExitCode::FAILURE
}
}
}
#[allow(clippy::print_stdout)]
fn run_app(state_initializer: &StateInitializer) -> ExitCode {
View::restore_terminal_on_panic();
match View::stdout() {
Err(e) => {
View::restore_terminal();
println!("Failed to initialize terminal: {e}");
ExitCode::FAILURE
}
Ok(mut view) => {
let result = app::run(state_initializer, &mut view);
let _ = view.close();
if let Err(e) = result {
println!("Runtime error: {e}");
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
},
}
}

View File

@@ -22,4 +22,12 @@ impl ActionResult {
Self::Nothing Self::Nothing
} }
} }
pub fn push_layer<T>(layer: T) -> Self where T: Layer + 'static {
Self::PushLayer(Box::new(layer))
}
pub fn replace_layer<T>(layer: T) -> Self where T: Layer + 'static {
Self::ReplaceLayer(Box::new(layer))
}
} }

72
src/state/event.rs Normal file
View File

@@ -0,0 +1,72 @@
use std::cell::RefCell;
use std::rc::Rc;
use crate::state::Environment;
pub trait Event<L> {
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult;
}
impl<L, F> Event<L> for F where F: Fn(& mut L, &Environment) -> EventResult {
fn dispatch(&self, layer: &mut L, environment: &Environment) -> EventResult {
self(layer, environment)
}
}
pub struct EventQueue<L> {
events: Rc<RefCell<Vec<Box<dyn Event<L>>>>>
}
impl<L> EventQueue<L> {
pub fn new() -> Self {
Self { events: Rc::new(RefCell::new(Vec::new())) }
}
pub fn rc_clone(&self) -> Self {
Self { events: Rc::clone(&self.events) }
}
pub fn enqueue<E: Event<L> + 'static>(&self, event: E) -> bool {
if let Ok(mut events) = self.events.try_borrow_mut() {
events.push(Box::new(event));
true
} else {
false
}
}
pub fn enqueue_fn<F>(&self, event: F) -> bool where F: Fn(&mut L, &Environment) -> EventResult + 'static {
self.enqueue(event)
}
pub fn take(&self) -> Vec<Box<dyn Event<L>>> {
self.events.take()
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum EventResult {
Nothing,
Draw,
Redraw,
}
impl EventResult {
pub const fn draw_if(condition: bool) -> Self {
if condition {
Self::Draw
} else {
Self::Nothing
}
}
pub fn merge(self, other: Self) -> Self {
if self == Self::Redraw || other == Self::Redraw {
Self::Redraw
} else if self == Self::Draw || other == Self::Draw {
Self::Draw
} else {
Self::Nothing
}
}
}

8
src/state/init.rs Normal file
View File

@@ -0,0 +1,8 @@
use std::path::Path;
use crate::component::filesystem::ActionKeyMap;
pub struct StateInitializer<'a> {
pub filesystem_start_path: &'a Path,
pub filesystem_action_map: &'static ActionKeyMap,
}

View File

@@ -1,9 +1,11 @@
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::Environment; use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::view::Frame; use crate::state::view::Frame;
pub trait Layer { pub trait Layer {
fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult; fn handle_input(&mut self, environment: &Environment, key_binding: KeyBinding) -> ActionResult;
fn handle_events(&mut self, environment: &Environment) -> EventResult;
fn render(&mut self, frame: &mut Frame); fn render(&mut self, frame: &mut Frame);
} }

View File

@@ -1,8 +1,8 @@
use std::path::Path;
use crate::component::filesystem::FsLayer; use crate::component::filesystem::FsLayer;
use crate::input::keymap::KeyBinding; use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult; use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::init::StateInitializer;
use crate::state::layer::Layer; use crate::state::layer::Layer;
use crate::state::view::Frame; use crate::state::view::Frame;
@@ -10,6 +10,8 @@ pub use self::environment::Environment;
mod environment; mod environment;
pub mod action; pub mod action;
pub mod event;
pub mod init;
pub mod layer; pub mod layer;
pub mod view; pub mod view;
@@ -19,15 +21,19 @@ pub struct State {
} }
impl State { impl State {
pub fn with_root_path(root_path: &Path, environment: Environment) -> Self { pub fn new(initializer: &StateInitializer, environment: Environment) -> Self {
Self { Self {
layers: vec![Box::new(FsLayer::with_root_path(root_path))], layers: vec![Box::new(FsLayer::new(initializer.filesystem_start_path, initializer.filesystem_action_map))],
environment environment
} }
} }
pub fn handle_events(&mut self) -> EventResult {
self.layers.iter_mut().fold(EventResult::Nothing, |result, layer| result.merge(layer.handle_events(&self.environment)))
}
pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult { pub fn handle_input(&mut self, key_binding: KeyBinding) -> ActionResult {
self.layers.last_mut().map(|layer| layer.handle_input(&self.environment, key_binding)).unwrap_or(ActionResult::Nothing) self.layers.last_mut().map_or(ActionResult::Nothing, |layer| layer.handle_input(&self.environment, key_binding))
} }
pub fn handle_resize(&mut self, width: u16, height: u16) { pub fn handle_resize(&mut self, width: u16, height: u16) {

View File

@@ -10,6 +10,7 @@ use ratatui::widgets::{StatefulWidget, Widget};
pub struct View { pub struct View {
term: Terminal<CrosstermBackend<Stdout>>, term: Terminal<CrosstermBackend<Stdout>>,
render_request: RenderRequest,
} }
impl View { impl View {
@@ -22,18 +23,22 @@ impl View {
term.hide_cursor()?; term.hide_cursor()?;
term.clear()?; term.clear()?;
Ok(Self { term }) Ok(Self { term, render_request: RenderRequest::Draw })
} }
pub fn restore_terminal_on_panic() { pub fn restore_terminal_on_panic() {
let prev_hook = panic::take_hook(); let prev_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| { panic::set_hook(Box::new(move |panic_info| {
let _ = terminal::disable_raw_mode(); Self::restore_terminal();
prev_hook(panic_info); prev_hook(panic_info);
})); }));
} }
pub fn restore_terminal() {
let _ = terminal::disable_raw_mode();
}
pub fn close(mut self) -> io::Result<()> { pub fn close(mut self) -> io::Result<()> {
self.term.show_cursor()?; self.term.show_cursor()?;
self.term.backend_mut().execute(terminal::LeaveAlternateScreen)?; self.term.backend_mut().execute(terminal::LeaveAlternateScreen)?;
@@ -45,11 +50,34 @@ impl View {
self.term.size() self.term.size()
} }
pub fn clear(&mut self) -> io::Result<()> { pub fn set_dirty(&mut self, full_redraw: bool) {
self.term.clear() let new_request = if full_redraw {
RenderRequest::Redraw
} else {
RenderRequest::Draw
};
self.render_request = self.render_request.merge(new_request);
} }
pub fn render<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) { pub fn render<R>(&mut self, renderer: R) -> io::Result<()> where R: FnOnce(&mut Frame) {
match self.render_request.consume() {
RenderRequest::Skip => {}
RenderRequest::Draw => {
self.draw(renderer)?;
}
RenderRequest::Redraw => {
self.term.clear()?;
self.draw(renderer)?;
}
}
Ok(())
}
fn draw<R>(&mut self, renderer: R) -> io::Result<CompletedFrame> where R: FnOnce(&mut Frame) {
self.term.draw(|frame| { self.term.draw(|frame| {
let mut frame = Frame::new(frame); let mut frame = Frame::new(frame);
renderer(&mut frame); renderer(&mut frame);
@@ -58,6 +86,29 @@ impl View {
} }
} }
#[derive(Copy, Clone, Eq, PartialEq)]
enum RenderRequest {
Skip,
Draw,
Redraw,
}
impl RenderRequest {
fn merge(self, other: Self) -> Self {
if self == Self::Redraw || other == Self::Redraw {
Self::Redraw
} else if self == Self::Draw || other == Self::Draw {
Self::Draw
} else {
Self::Skip
}
}
fn consume(&mut self) -> Self {
std::mem::replace(self, Self::Skip)
}
}
pub struct Frame<'a, 'b> { pub struct Frame<'a, 'b> {
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>, inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
cursor: Option<(u16, u16)>, cursor: Option<(u16, u16)>,

View File

@@ -1,4 +1,3 @@
pub use integer_length::int_len; pub use integer_length::int_len;
mod integer_length; mod integer_length;
pub mod slab_tree;

View File

@@ -1,61 +0,0 @@
use slab_tree::{NodeId, NodeMut, NodeRef};
pub trait NodeRefExtensions {
fn parent_id(&self) -> Option<NodeId>;
fn first_child_id(&self) -> Option<NodeId>;
fn last_child_id(&self) -> Option<NodeId>;
fn prev_sibling_id(&self) -> Option<NodeId>;
fn next_sibling_id(&self) -> Option<NodeId>;
}
pub trait NodeMutExtensions {
fn parent_id(&mut self) -> Option<NodeId>;
fn first_child_id(&mut self) -> Option<NodeId>;
fn last_child_id(&mut self) -> Option<NodeId>;
fn prev_sibling_id(&mut self) -> Option<NodeId>;
fn next_sibling_id(&mut self) -> Option<NodeId>;
}
impl<'a, T> NodeRefExtensions for NodeRef<'a, T> {
fn parent_id(&self) -> Option<NodeId> {
self.parent().map(|node| node.node_id())
}
fn first_child_id(&self) -> Option<NodeId> {
self.first_child().map(|node| node.node_id())
}
fn last_child_id(&self) -> Option<NodeId> {
self.last_child().map(|node| node.node_id())
}
fn prev_sibling_id(&self) -> Option<NodeId> {
self.prev_sibling().map(|node| node.node_id())
}
fn next_sibling_id(&self) -> Option<NodeId> {
self.next_sibling().map(|node| node.node_id())
}
}
impl<'a, T> NodeMutExtensions for NodeMut<'a, T> {
fn parent_id(&mut self) -> Option<NodeId> {
self.parent().map(|node| node.node_id())
}
fn first_child_id(&mut self) -> Option<NodeId> {
self.first_child().map(|node| node.node_id())
}
fn last_child_id(&mut self) -> Option<NodeId> {
self.last_child().map(|node| node.node_id())
}
fn prev_sibling_id(&mut self) -> Option<NodeId> {
self.prev_sibling().map(|node| node.node_id())
}
fn next_sibling_id(&mut self) -> Option<NodeId> {
self.next_sibling().map(|node| node.node_id())
}
}