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

Compare commits

16 Commits

Author SHA1 Message Date
63b56de63f Move custom slab-tree node logic into included sources 2023-08-27 14:46:19 +02:00
dace27d259 Move slab-tree extensions to the included sources 2023-08-27 13:34:50 +02:00
5f332677a7 Include slab-tree package sources in the repository 2023-08-27 13:34:39 +02:00
995e0fc8f8 Move filesystem tree functions out of the layer implementation 2023-08-27 11:55:06 +02:00
020439d813 Migrate old filesystem events to new generic layer events 2023-08-27 09:17:19 +02:00
d04fc62033 Introduce layer events 2023-08-27 09:17:19 +02:00
38edfff874 Support longer prefix strings in input field overlay 2023-08-26 15:53:52 +02:00
df04593aab Replace verbose Box constructions with generics 2023-08-26 14:09:36 +02:00
95c12d4f61 Close input field overlay when pressing backspace with empty input field 2023-08-06 04:23:50 +02:00
e59ecf2f08 Add prefix string to input field overlay 2023-08-06 04:21:16 +02:00
c27d9d7401 Change input field text color to black 2023-08-06 04:21:15 +02:00
c9fe9f8f0e Prevent cursor jumping after deleting a file by re-selecting file that was below the deleted file (instead of above) 2023-08-05 04:38:43 +02:00
bdd25b89e3 Move wsl.sh to scripts folder 2023-08-05 04:19:52 +02:00
19623d745a Update wsl.sh to install cmake from default repositories 2023-08-05 03:45:07 +02:00
c44c0691c9 Add README 2023-08-05 03:44:55 +02:00
7519472a43 Add build scripts 2023-08-03 19:52:21 +02:00
60 changed files with 4395 additions and 441 deletions

BIN
.github/readme/screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/.idea/
/out/
/target/

1
Cargo.lock generated
View File

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

View File

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

View File

@@ -2,10 +2,8 @@ FROM rust:1.71.0 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
RUN ./scripts/build.sh
FROM scratch as exporter
COPY --from=builder /app/target/release/bark .
# docker build --output out .
COPY --from=builder /app/out/ .

50
README.md Normal file
View File

@@ -0,0 +1,50 @@
# Bark
`bark` is a tree-based terminal filesystem browser and file manager with `vim`-style key bindings.
![Bark Browser Screenshot](.github/readme/screenshot.png)
# Features
- `ls`-style file listing
- `vim`-style navigation adapted for tree hierarchies
- Basic file management (create, rename, edit, delete)
- Support for Linux and Windows
See [action/mod.rs](https://github.com/chylex/Bark-Browser/blob/main/src/component/filesystem/action/mod.rs) for an up-to-date list of all key bindings.
# Roadmap
- Settings
- File search
- Visual mode for selecting multiple files
- Ex commands for more complex operations
- Directory statistics (total size, number of files, etc.)
- Tree filtering (views that only include certain files)
- Rebindable keys and macros
# Building
1. Install [Rust](https://www.rust-lang.org/tools/install).
2. Run `cargo run` to launch the application.
3. Run `scripts/build.sh` or `scripts/build.bat` to build a release binary into the `out/` folder.
## Windows Subsystem for Linux
Run `scripts/wsl.sh` from a Debian-based WSL environment to quickly install Rust and CMake into WSL.
## Docker
Run `docker build --output out .` to build a release binary into the `out/` folder on the host. BuildKit is required.
# Contributing
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).
# 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);
}
}

22
scripts/build.ps1 Normal file
View File

@@ -0,0 +1,22 @@
Set-Location (Split-Path $PSScriptRoot -Parent)
if (!(Test-Path "out")) {
mkdir "out"
}
cargo build --release
$version = & "target\release\bark.exe" --version
$arch = switch ((Get-WMIObject -Class Win32_Processor).Architecture) {
0 { "x86" }
1 { "mips" }
2 { "alpha" }
3 { "ppc" }
5 { "arm" }
6 { "ia64" }
9 { "x64" }
12 { "arm64" }
DEFAULT { "unknown" }
}
Compress-Archive -Force -Path "target\release\bark.exe" -DestinationPath "out\bark-${version}-windows-${arch}.zip"

20
scripts/build.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
cd ..
mkdir -p out
cargo build --release
cd target/release
VERSION=$(./bark --version)
ARCH=$(uname -m)
if [[ "$ARCH" == "x86_64" ]]; then
ARCH="x64"
elif [[ "$ARCH" == "aarch64" ]]; then
ARCH="arm64"
fi
tar czf "../../out/bark-${VERSION}-linux-${ARCH}.tar.gz" --owner=0 --group=0 -- bark

7
wsl.sh → scripts/wsl.sh Normal file → Executable file
View File

@@ -6,6 +6,7 @@ sudo apt-get install -y \
apt-transport-https \
build-essential \
ca-certificates \
cmake \
gdb \
gnupg \
software-properties-common \
@@ -15,9 +16,3 @@ sudo apt-get install -y \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup component add rust-src
# CMake
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add -
sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ bionic main'
sudo apt-get update
sudo apt-get install -y cmake

View File

@@ -5,6 +5,7 @@ use crossterm::event::{Event, KeyEventKind};
use crate::input::keymap::KeyBinding;
use crate::state::{Environment, State};
use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::view::View;
pub fn run(start_path: &Path) -> std::io::Result<()> {
@@ -14,41 +15,55 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
let environment = Environment::try_from(&view)?;
let mut state = State::with_root_path(start_path, environment);
'render: loop {
loop {
match state.handle_events() {
EventResult::Nothing => {}
EventResult::Draw => {
view.set_dirty(false);
}
EventResult::Redraw => {
view.set_dirty(true);
}
}
view.render(|frame| state.render(frame))?;
'event: loop {
match handle_event(&mut state, crossterm::event::read()?) {
ActionResult::Nothing => {
continue 'event;
}
ActionResult::Draw => {
continue 'render;
}
ActionResult::Redraw => {
view.clear()?;
continue 'render;
}
ActionResult::PushLayer(layer) => {
state.push_layer(layer);
continue 'render;
}
ActionResult::ReplaceLayer(layer) => {
state.pop_layer();
state.push_layer(layer);
continue 'render;
}
ActionResult::PopLayer => {
if state.pop_layer() {
break 'render;
} else {
continue 'render;
}
match handle_terminal_event(&mut state, crossterm::event::read()?) {
ActionResult::Nothing => {
continue;
}
ActionResult::Draw => {
view.set_dirty(false);
continue;
}
ActionResult::Redraw => {
view.set_dirty(true);
continue;
}
ActionResult::PushLayer(layer) => {
state.push_layer(layer);
view.set_dirty(false);
continue;
}
ActionResult::ReplaceLayer(layer) => {
state.pop_layer();
state.push_layer(layer);
view.set_dirty(false);
continue;
}
ActionResult::PopLayer => {
if state.pop_layer() {
break;
} else {
view.set_dirty(false);
continue;
}
}
}
@@ -58,7 +73,7 @@ pub fn run(start_path: &Path) -> std::io::Result<()> {
}
#[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 key.kind == KeyEventKind::Release {
ActionResult::Nothing

View File

@@ -69,7 +69,7 @@ impl<'a> InputFieldDialogBuilder5<'a> {
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 step3 = step4.step3;
let step2 = step3.step2;

View File

@@ -12,6 +12,7 @@ use crate::component::input::InputField;
use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult;
use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer;
use crate::state::view::Frame;
@@ -29,8 +30,9 @@ pub struct 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 confirm_action = Box::new(confirm_action);
Self { y, min_width, default_color, darker_color, title, message, field, confirm_action }
}
@@ -53,15 +55,15 @@ impl<'a> Layer for InputFieldDialogLayer<'a> {
}
_ => {
if self.field.handle_input(key_binding) {
ActionResult::Draw
} else {
ActionResult::Nothing
}
ActionResult::draw_if(self.field.handle_input(key_binding))
}
}
}
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) {
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);

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();
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));
Self::new(map, vec![

View File

@@ -63,7 +63,7 @@ impl<'a> MessageDialogBuilder4<'a> {
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))
}
}

View File

@@ -10,6 +10,7 @@ use crate::component::dialog::render_dialog_border;
use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult;
use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer;
use crate::state::view::Frame;
@@ -40,7 +41,7 @@ impl<'a> MessageDialogLayer<'a> {
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()
.y(y)
.color(Color::LightRed)
@@ -55,6 +56,10 @@ impl Layer for MessageDialogLayer<'_> {
self.actions.handle_input(key_binding)
}
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) {
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);

View File

@@ -17,7 +17,7 @@ impl Action<FsLayer> for PushCountDigit {
if new_count > MAX_COUNT {
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 {
layer.registers.count = Some(new_count);
ActionResult::Nothing

View File

@@ -1,19 +1,17 @@
use std::{fs, io};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use ratatui::style::Color;
use slab_tree::NodeId;
use crate::component::dialog::input::InputFieldDialogLayer;
use crate::component::dialog::message::MessageDialogLayer;
use crate::component::filesystem::action::file::{FileNode, get_selected_file};
use crate::component::filesystem::event::FsLayerEvent;
use crate::component::filesystem::action::file::{FileNode, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::FsLayer;
use crate::file::FileKind;
use crate::state::action::{Action, ActionResult};
use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
trait CreateEntry {
fn title() -> &'static str;
@@ -71,7 +69,7 @@ impl Action<FsLayer> for CreateDirectoryInSelectedDirectory {
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)) {
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 {
ActionResult::Nothing
}
@@ -95,7 +93,7 @@ impl Action<FsLayer> for CreateDirectoryInParentOfSelectedEntry {
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) {
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 {
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()?)) })
}
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 pending_events = Rc::clone(&layer.pending_events);
let events = layer.events();
InputFieldDialogLayer::build()
.y(y)
@@ -115,25 +113,24 @@ fn create_new_name_prompt<'b, T: CreateEntry>(layer: &FsLayer, parent_folder: Pa
.color(Color::LightCyan, Color::Cyan)
.title(T::title())
.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() {
return ActionResult::Nothing;
}
let new_path = parent_folder.join(&new_name);
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) {
Ok(_) => {
FsLayerEvent::RefreshViewNodeChildren(view_node_id_to_refresh).enqueue(&pending_events);
FsLayerEvent::SelectViewNodeChildByFileName(view_node_id_to_refresh, new_name).enqueue(&pending_events);
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
ActionResult::PopLayer
}
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::path::{Path, PathBuf};
use std::rc::Rc;
use std::time::{Duration, Instant};
use ratatui::style::Color;
@@ -9,18 +8,18 @@ use slab_tree::NodeId;
use crate::component::dialog::message::MessageDialogLayer;
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::file::{FileEntry, FileKind};
use crate::state::action::{Action, ActionResult};
use crate::state::Environment;
use crate::state::event::EventResult;
pub struct DeleteSelectedEntry;
impl Action<FsLayer> for DeleteSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
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 {
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> {
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) {
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!("This will affect {}.", total_files.describe())),
])
.yes_no(Box::new(move || {
.yes_no(move || {
match delete_path_recursively(&path) {
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
}
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);

View File

@@ -8,12 +8,11 @@ use slab_tree::NodeRef;
use crate::component::dialog::message::MessageDialogLayer;
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::tree::FsTreeViewNode;
use crate::state::action::{Action, ActionResult};
use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
use crate::state::event::EventResult;
pub struct EditSelectedEntry;
@@ -34,12 +33,12 @@ fn open_default_editor(layer: &FsLayer, node: &NodeRef<FsTreeViewNode>, path: &P
.status();
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.
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
}

View File

@@ -1,11 +1,13 @@
use std::io;
use std::path::Path;
use slab_tree::NodeRef;
use slab_tree::{NodeId, NodeRef};
use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::{FileEntry, FileKind};
use crate::state::Environment;
use crate::state::event::{Event, EventResult};
pub use self::create::*;
pub use self::delete::*;
@@ -18,7 +20,7 @@ mod edit;
mod rename;
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(path) = entry.path() {
return Some(FileNode { node, entry, path });
@@ -54,3 +56,19 @@ fn format_io_error(err: &io::Error) -> String {
str.push('.');
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 std::{fs, io};
use std::path::PathBuf;
use std::rc::Rc;
use ratatui::style::Color;
use slab_tree::NodeRef;
use crate::component::dialog::input::InputFieldDialogLayer;
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::event::FsLayerEvent;
use crate::component::filesystem::action::file::{FileNode, format_io_error, get_entry_kind_name, get_selected_file, RefreshParentDirectoryAndSelectFile};
use crate::component::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTreeViewNode;
use crate::file::FileEntry;
use crate::state::action::{Action, ActionResult};
use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
pub struct RenameSelectedEntry {
pub prefill: bool,
@@ -24,7 +22,7 @@ pub struct RenameSelectedEntry {
impl Action<FsLayer> for RenameSelectedEntry {
fn perform(&self, layer: &mut FsLayer, _environment: &Environment) -> ActionResult {
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 {
ActionResult::Nothing
}
@@ -35,8 +33,8 @@ impl RenameSelectedEntry {
#[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> {
let y = layer.dialog_y();
let parent_node_id = node.parent_id();
let pending_events = Rc::clone(&layer.pending_events);
let events = layer.events();
let parent_view_node_id = node.parent_id();
InputFieldDialogLayer::build()
.y(y)
@@ -45,20 +43,19 @@ impl RenameSelectedEntry {
.title(format!("Rename {}", get_entry_kind_name(entry)))
.message(format!("Renaming {}", path.to_string_lossy()))
.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) {
Ok(_) => {
if let Some(parent_node_id) = parent_node_id {
FsLayerEvent::RefreshViewNodeChildren(parent_node_id).enqueue(&pending_events);
FsLayerEvent::SelectViewNodeChildByFileName(parent_node_id, new_name).enqueue(&pending_events);
if let Some(parent_view_node_id) = parent_view_node_id {
events.enqueue(RefreshParentDirectoryAndSelectFile { parent_view_node_id, child_file_name: new_name });
}
ActionResult::PopLayer
}
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,14 +1,15 @@
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::tree::FsTree;
use crate::state::Environment;
pub struct ExpandSelectedOr<M: SimpleMovementAction>(pub M);
impl<M: SimpleMovementAction> MovementAction for ExpandSelectedOr<M> {
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> {
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 {
perform_movement_with_count(layer, layer.registers.count, |layer, node_id| {
if action(layer, node_id) {
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_from_register(layer, |tree, node_id| {
if action(tree, node_id) {
Some(node_id)
} 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 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::tree::{FsTreeView, FsTreeViewNode};
use crate::component::filesystem::tree::{FsTree, FsTreeViewNode};
use crate::state::Environment;
use crate::util::slab_tree::NodeRefExtensions;
pub struct 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()
}
}
@@ -17,7 +16,7 @@ impl SimpleMovementAction for MoveToNextSibling {
pub struct 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()
}
}
@@ -25,7 +24,7 @@ impl SimpleMovementAction for MoveToPreviousSibling {
pub struct 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() {
selected_node.parent().and_then(|node| node.first_child_id())
} else {
@@ -37,7 +36,7 @@ impl SimpleMovementAction for MoveBetweenFirstAndLastSibling {
pub struct 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()
}
}
@@ -46,19 +45,17 @@ pub struct MoveOrTraverseUpParent;
impl MovementAction for MoveOrTraverseUpParent {
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 {
fn get_target(layer: &mut FsLayer, node_id: NodeId) -> Option<NodeId> {
let view = &layer.tree.view;
if let Some(node) = view.get(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(view, &node);
fn get_target(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
if let Some(node) = tree.get_view_node(node_id) {
let target_node_id = <MoveToParent as SimpleMovementAction>::get_target(&node);
if target_node_id.is_some() {
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)
}
}

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

View File

@@ -1,7 +1,7 @@
use slab_tree::{NodeId, NodeRef};
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::Environment;
@@ -25,7 +25,7 @@ pub trait MovementAction {
impl<T: MovementAction> Action<FsLayer> for T {
fn perform(&self, layer: &mut FsLayer, environment: &Environment) -> ActionResult {
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
} else {
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> {
perform_movement_with_count_from(layer, count, layer.selected_view_node_id, get_target)
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(&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;
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;
} else {
break;
@@ -52,16 +56,15 @@ fn perform_movement_with_count_from<F>(layer: &mut FsLayer, count: Option<usize>
}
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 {
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> {
let view = &layer.tree.view;
view.get(node_id).and_then(|node| T::get_target(view, &node))
fn get_simple_movement_target<T: SimpleMovementAction>(tree: &mut FsTree, node_id: NodeId) -> Option<NodeId> {
tree.get_view_node(node_id).and_then(|node| T::get_target(&node))
}

View File

@@ -37,7 +37,7 @@ impl<A: SimpleMovementAction + 'static, C: MovementCount> MovementWithCountFacto
impl<A: SimpleMovementAction, C: MovementCount> MovementAction for MovementWithCount<A, C> {
fn get_target(&self, layer: &mut FsLayer, environment: &Environment) -> Option<NodeId> where Self: Sized {
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 crate::component::filesystem::action::movement::SimpleMovementAction;
use crate::component::filesystem::tree::{FsTreeView, FsTreeViewNode};
use crate::component::filesystem::tree::FsTreeViewNode;
/// A movement action with fallback.
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> {
fn get_target(view: &FsTreeView, selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
A::get_target(view, selected_node).or_else(|| F::get_target(view, selected_node))
fn get_target(selected_node: &NodeRef<FsTreeViewNode>) -> Option<NodeId> where Self: Sized {
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::filesystem::FsLayer;
use crate::component::filesystem::tree::FsTree;
use crate::state::action::{Action, ActionResult};
use crate::state::Environment;
@@ -19,19 +20,19 @@ impl Action<FsLayer> for ExpandCollapse {
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 let Some(node) = layer.selected_node() {
if let Some(node) = layer.tree.selected_node() {
if node.data().is_expanded() {
let child_node_ids = node.children().map(|node| node.node_id()).collect();
let remaining_depth = depth.saturating_sub(1);
if !expand_children_to_depth(layer, child_node_ids, remaining_depth) {
return ActionResult::PushLayer(Box::new(MessageDialogLayer::build()
if !expand_children_to_depth(&mut layer.tree, child_node_ids, remaining_depth) {
return ActionResult::push_layer(MessageDialogLayer::build()
.y(layer.dialog_y())
.color(Color::LightYellow)
.title("Expansion Stopped")
.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);
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 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 {
let node_id = *node_id;
layer.tree.expand(node_id);
get_child_node_ids(layer, node_id, &mut child_node_ids);
tree.expand(node_id);
get_child_node_ids(tree, node_id, &mut child_node_ids);
if start_time.elapsed() >= MAX_EXPANSION_TIME {
return false;
@@ -67,8 +68,8 @@ fn expand_children_to_depth(layer: &mut FsLayer, mut child_node_ids: Vec<NodeId>
true
}
fn get_child_node_ids(layer: &FsLayer, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
if let Some(node) = layer.tree.view.get(node_id) {
fn get_child_node_ids(tree: &FsTree, node_id: NodeId, output_node_ids: &mut Vec<NodeId>) {
if let Some(node) = tree.get_view_node(node_id) {
for child in node.children() {
output_node_ids.push(child.node_id());
}

View File

@@ -6,7 +6,7 @@ pub struct RefreshChildrenOfSelected;
impl Action<FsLayer> for RefreshChildrenOfSelected {
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
} else {
ActionResult::Nothing

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_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,32 +1,27 @@
use std::cell::RefCell;
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::tree::{FsTree, FsTreeViewNode};
use crate::component::filesystem::tree::FsTree;
use crate::file::FileOwnerNameCache;
use crate::input::keymap::{KeyBinding, KeyMapLookupResult};
use crate::state::action::ActionResult;
use crate::state::Environment;
use crate::state::event::{EventQueue, EventResult};
use crate::state::layer::Layer;
use crate::state::view::Frame;
mod action;
mod event;
mod render;
mod tree;
mod registers;
pub struct FsLayer {
pub tree: FsTree,
pub selected_view_node_id: NodeId,
tree_structure_version: u32,
pub registers: FsTreeRegisters,
cursor_y: u16,
pending_keys: Vec<KeyBinding>,
pending_events: FsLayerPendingEvents,
event_queue: EventQueue<FsLayer>,
file_owner_name_cache: FileOwnerNameCache,
column_width_cache: Option<ColumnWidths>,
}
@@ -36,70 +31,25 @@ impl FsLayer {
// 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 {
tree,
selected_view_node_id: root_id,
tree: FsTree::with_root_path(root_path),
tree_structure_version: 0,
cursor_y: 0,
registers: FsTreeRegisters::new(),
pending_keys: Vec::new(),
pending_events: Rc::new(RefCell::new(Vec::new())),
event_queue: EventQueue::new(),
file_owner_name_cache: FileOwnerNameCache::new(),
column_width_cache: None,
}
}
pub fn events(&self) -> EventQueue<Self> {
self.event_queue.rc_clone()
}
pub const fn dialog_y(&self) -> u16 {
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 {
@@ -133,9 +83,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) {
for event in self.pending_events.take() {
event.handle(self);
if self.tree_structure_version != self.tree.structure_version() {
self.tree_structure_version = self.tree.structure_version();
self.column_width_cache.take();
}
render::render(self, frame);

View File

@@ -7,7 +7,7 @@ use ratatui::widgets::{Clear, Widget};
use slab_tree::{NodeId, NodeRef};
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::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 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;
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 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());
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 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();
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);
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));
cursor_y = cursor_y.saturating_add(1);
}
@@ -81,7 +81,7 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
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));
}
@@ -94,12 +94,11 @@ fn collect_displayed_rows(tree: &FsTree, selected_node_id: NodeId, terminal_rows
(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> {
let view = &tree.view;
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 next_node = cursor
.and_then(|id| view.get(id))
.and_then(|node| func(view, &node))
.and_then(|id| view.get(id));
.and_then(|id| tree.get_view_node(id))
.and_then(func)
.and_then(|id| tree.get_view_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 crate::component::filesystem::tree::view::FsTreeViewIterator;
use crate::file::FileEntry;
pub use self::model::FsTreeModel;
@@ -13,16 +14,47 @@ mod model;
mod view;
pub struct FsTree {
pub model: FsTreeModel,
pub view: FsTreeView,
model: FsTreeModel,
view: FsTreeView,
pub selected_view_node_id: NodeId,
structure_version: u32,
}
impl FsTree {
pub fn with_root_path(path: &Path) -> Self {
let model = FsTreeModel::with_root_path(path);
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> {
@@ -32,26 +64,79 @@ impl FsTree {
}
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 {
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 {
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> {
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 {
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 {
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::file::FileEntry;
use crate::util::slab_tree::{NodeMutExtensions, NodeRefExtensions};
impl FsTreeModel {
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;
mod above_below;
pub use self::iterator::FsTreeViewIterator;
mod expand_collapse;
mod iterator;
mod refresh;

View File

@@ -4,13 +4,13 @@ use crate::component::filesystem::tree::{FsTreeModel, FsTreeView, FsTreeViewNode
impl FsTreeView {
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() {
self.set_root(new_model_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)
} else {
None

View File

@@ -209,7 +209,7 @@ impl<'a> StatefulWidget for InputFieldWidget<'a> {
}
let style = Style::default()
.fg(Color::White)
.fg(Color::Black)
.bg(self.default_background);
Clear.render(area, buf);
@@ -222,7 +222,7 @@ impl<'a> StatefulWidget for InputFieldWidget<'a> {
if has_truncated_end {
buf.get_mut(area.right().saturating_sub(1), area.y)
.set_char('~')
.set_fg(Color::White)
.set_fg(Color::Black)
.set_bg(self.trimmed_background);
}

View File

@@ -1,28 +1,34 @@
use std::cmp::min;
use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::style::Color;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::text::Span;
use ratatui::widgets::Paragraph;
use crate::component::input::InputField;
use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult;
use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::layer::Layer;
use crate::state::view::Frame;
pub struct InputFieldOverlayLayer {
pub struct InputFieldOverlayLayer<'a> {
field: InputField,
read_only_prefix: &'a str,
confirm_action: Box<dyn Fn(String) -> ActionResult>,
}
impl InputFieldOverlayLayer {
pub fn new(confirm_action: Box<dyn Fn(String) -> ActionResult>) -> Self {
Self {
field: InputField::new(),
confirm_action,
}
impl<'a> InputFieldOverlayLayer<'a> {
pub fn new<F>(read_only_prefix: &'a str, confirm_action: F) -> Self where F: Fn(String) -> ActionResult + 'static {
let field = InputField::new();
let confirm_action = Box::new(confirm_action);
Self { field, read_only_prefix, confirm_action }
}
}
impl Layer for InputFieldOverlayLayer {
impl<'a> Layer for InputFieldOverlayLayer<'a> {
#[allow(clippy::wildcard_enum_match_arm)]
fn handle_input(&mut self, _environment: &Environment, key_binding: KeyBinding) -> ActionResult {
match (key_binding.code(), key_binding.modifiers()) {
@@ -35,18 +41,49 @@ impl Layer for InputFieldOverlayLayer {
(self.confirm_action)(self.field.text().to_owned())
}
_ => {
if self.field.handle_input(key_binding) {
ActionResult::Draw
(KeyCode::Backspace, KeyModifiers::NONE) => {
if self.field.text().is_empty() {
ActionResult::PopLayer
} else {
ActionResult::Nothing
ActionResult::draw_if(self.field.handle_input(key_binding))
}
}
_ => {
ActionResult::draw_if(self.field.handle_input(key_binding))
}
}
}
fn handle_events(&mut self, _environment: &Environment) -> EventResult {
EventResult::Nothing
}
fn render(&mut self, frame: &mut Frame) {
let size = frame.size();
self.field.render(frame, size.x, size.bottom().saturating_sub(1), size.width, Color::LightYellow, Color::Yellow);
if size.width < 1 || size.height < 1 {
return;
}
let x = size.x;
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()
.fg(Color::Black)
.bg(Color::LightYellow);
let prefix_paragraph = Paragraph::new(self.read_only_prefix)
.style(prefix_style);
frame.render_widget(prefix_paragraph, Rect { x, y, width: prefix_width, height: 1 });
}
if size.width > prefix_width {
self.field.render(frame, x.saturating_add(prefix_width), y, size.width.saturating_sub(prefix_width), Color::LightYellow, Color::Yellow);
}
}
}

View File

@@ -13,3 +13,21 @@ pub enum ActionResult {
ReplaceLayer(Box<dyn Layer>),
PopLayer,
}
impl ActionResult {
pub const fn draw_if(condition: bool) -> Self {
if condition {
Self::Draw
} else {
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
}
}
}

View File

@@ -1,9 +1,11 @@
use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult;
use crate::state::Environment;
use crate::state::event::EventResult;
use crate::state::view::Frame;
pub trait Layer {
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);
}

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use crate::component::filesystem::FsLayer;
use crate::input::keymap::KeyBinding;
use crate::state::action::ActionResult;
use crate::state::event::EventResult;
use crate::state::layer::Layer;
use crate::state::view::Frame;
@@ -10,6 +11,7 @@ pub use self::environment::Environment;
mod environment;
pub mod action;
pub mod event;
pub mod layer;
pub mod view;
@@ -26,8 +28,12 @@ impl State {
}
}
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 {
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) {

View File

@@ -10,6 +10,7 @@ use ratatui::widgets::{StatefulWidget, Widget};
pub struct View {
term: Terminal<CrosstermBackend<Stdout>>,
render_request: RenderRequest,
}
impl View {
@@ -22,7 +23,7 @@ impl View {
term.hide_cursor()?;
term.clear()?;
Ok(Self { term })
Ok(Self { term, render_request: RenderRequest::Draw })
}
pub fn restore_terminal_on_panic() {
@@ -45,11 +46,34 @@ impl View {
self.term.size()
}
pub fn clear(&mut self) -> io::Result<()> {
self.term.clear()
pub fn set_dirty(&mut self, full_redraw: bool) {
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| {
let mut frame = Frame::new(frame);
renderer(&mut frame);
@@ -58,6 +82,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> {
inner: &'a mut ratatui::Frame<'b, CrosstermBackend<Stdout>>,
cursor: Option<(u16, u16)>,

View File

@@ -1,4 +1,3 @@
pub use integer_length::int_len;
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())
}
}