Skip to content

Commit

Permalink
feat: generic identifier (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
EdJoPaTo authored Oct 30, 2023
1 parent 58a80b6 commit b914819
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 120 deletions.
23 changes: 17 additions & 6 deletions examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,26 @@ impl<'a> App<'a> {
fn new() -> Self {
Self {
tree: StatefulTree::with_items(vec![
TreeItem::new_leaf("a"),
TreeItem::new_leaf("a", "Alfa"),
TreeItem::new(
"b",
"Bravo",
vec![
TreeItem::new_leaf("c"),
TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]),
TreeItem::new_leaf("g"),
TreeItem::new_leaf("c", "Charlie"),
TreeItem::new(
"d",
"Delta",
vec![
TreeItem::new_leaf("e", "Echo"),
TreeItem::new_leaf("f", "Foxtrot"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("g", "Golf"),
],
),
TreeItem::new_leaf("h"),
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("h", "Hotel"),
]),
}
}
Expand Down Expand Up @@ -74,6 +84,7 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
let area = f.size();

let items = Tree::new(app.tree.items.clone())
.expect("all item identifiers are unique")
.block(
Block::new()
.borders(Borders::ALL)
Expand Down
8 changes: 4 additions & 4 deletions examples/util/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use tui_tree_widget::{TreeItem, TreeState};

pub struct StatefulTree<'a> {
pub state: TreeState,
pub items: Vec<TreeItem<'a>>,
pub state: TreeState<&'static str>,
pub items: Vec<TreeItem<'a, &'static str>>,
}

impl<'a> StatefulTree<'a> {
Expand All @@ -14,15 +14,15 @@ impl<'a> StatefulTree<'a> {
}
}

pub fn with_items(items: Vec<TreeItem<'a>>) -> Self {
pub fn with_items(items: Vec<TreeItem<'a, &'static str>>) -> Self {
Self {
state: TreeState::default(),
items,
}
}

pub fn first(&mut self) {
self.state.select_first();
self.state.select_first(&self.items);
}

pub fn last(&mut self) {
Expand Down
96 changes: 45 additions & 51 deletions src/flatten.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::identifier::{TreeIdentifier, TreeIdentifierVec};
use crate::TreeItem;

/// A flattened item of all visible [`TreeItem`s](TreeItem).
///
/// Generated via [`flatten`].
pub struct Flattened<'a> {
pub identifier: Vec<usize>,
pub item: &'a TreeItem<'a>,
pub struct Flattened<'a, Identifier> {
pub identifier: Vec<Identifier>,
pub item: &'a TreeItem<'a, Identifier>,
}

impl<'a> Flattened<'a> {
impl<'a, Identifier> Flattened<'a, Identifier> {
#[must_use]
pub fn depth(&self) -> usize {
self.identifier.len() - 1
Expand All @@ -18,21 +17,30 @@ impl<'a> Flattened<'a> {

/// Get a flat list of all visible [`TreeItem`s](TreeItem).
#[must_use]
pub fn flatten<'a>(opened: &[TreeIdentifierVec], items: &'a [TreeItem<'a>]) -> Vec<Flattened<'a>> {
pub fn flatten<'a, Identifier>(
opened: &[Vec<Identifier>],
items: &'a [TreeItem<'a, Identifier>],
) -> Vec<Flattened<'a, Identifier>>
where
Identifier: Clone + PartialEq,
{
internal(opened, items, &[])
}

#[must_use]
fn internal<'a>(
opened: &[TreeIdentifierVec],
items: &'a [TreeItem<'a>],
current: TreeIdentifier,
) -> Vec<Flattened<'a>> {
fn internal<'a, Identifier>(
opened: &[Vec<Identifier>],
items: &'a [TreeItem<'a, Identifier>],
current: &[Identifier],
) -> Vec<Flattened<'a, Identifier>>
where
Identifier: Clone + PartialEq,
{
let mut result = Vec::new();

for (index, item) in items.iter().enumerate() {
for item in items {
let mut child_identifier = current.to_vec();
child_identifier.push(index);
child_identifier.push(item.identifier.clone());

result.push(Flattened {
item,
Expand All @@ -49,76 +57,62 @@ fn internal<'a>(
}

#[cfg(test)]
fn get_naive_string_from_text(text: &ratatui::text::Text<'_>) -> String {
text.lines
.first()
.unwrap()
.spans
.first()
.unwrap()
.content
.to_string()
}

#[cfg(test)]
fn get_example_tree_items() -> Vec<TreeItem<'static>> {
fn get_example_tree_items() -> Vec<TreeItem<'static, &'static str>> {
vec![
TreeItem::new_leaf("a"),
TreeItem::new_leaf("a", "Alfa"),
TreeItem::new(
"b",
"Bravo",
vec![
TreeItem::new_leaf("c"),
TreeItem::new("d", vec![TreeItem::new_leaf("e"), TreeItem::new_leaf("f")]),
TreeItem::new_leaf("g"),
TreeItem::new_leaf("c", "Charlie"),
TreeItem::new(
"d",
"Delta",
vec![
TreeItem::new_leaf("e", "Echo"),
TreeItem::new_leaf("f", "Foxtrot"),
],
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("g", "Golf"),
],
),
TreeItem::new_leaf("h"),
)
.expect("all item identifiers are unique"),
TreeItem::new_leaf("h", "Hotel"),
]
}

#[test]
fn get_opened_nothing_opened_is_top_level() {
let items = get_example_tree_items();
let result = flatten(&[], &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.collect::<Vec<_>>();
let result_text = result.iter().map(|o| o.item.identifier).collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "h"]);
}

#[test]
fn get_opened_wrong_opened_is_only_top_level() {
let items = get_example_tree_items();
let opened = [vec![0], vec![1, 1]];
let opened = [vec!["a"], vec!["b", "d"]];
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.collect::<Vec<_>>();
let result_text = result.iter().map(|o| o.item.identifier).collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "h"]);
}

#[test]
fn get_opened_one_is_opened() {
let items = get_example_tree_items();
let opened = [vec![1]];
let opened = [vec!["b"]];
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.collect::<Vec<_>>();
let result_text = result.iter().map(|o| o.item.identifier).collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "c", "d", "g", "h"]);
}

#[test]
fn get_opened_all_opened() {
let items = get_example_tree_items();
let opened = [vec![1], vec![1, 1]];
let opened = [vec!["b"], vec!["b", "d"]];
let result = flatten(&opened, &items);
let result_text = result
.iter()
.map(|o| get_naive_string_from_text(&o.item.text))
.collect::<Vec<_>>();
let result_text = result.iter().map(|o| o.item.identifier).collect::<Vec<_>>();
assert_eq!(result_text, ["a", "b", "c", "d", "e", "f", "g", "h"]);
}
23 changes: 9 additions & 14 deletions src/identifier.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
#![allow(clippy::module_name_repetitions)]

/// Reference to a [`TreeItem`](crate::TreeItem) in a [`Tree`](crate::Tree)
pub type TreeIdentifier<'a> = &'a [usize];
/// Reference to a [`TreeItem`](crate::TreeItem) in a [`Tree`](crate::Tree)
pub type TreeIdentifierVec = Vec<usize>;

/// Split a [`TreeIdentifier`] into its branch and leaf.
/// Split an `Identifier` into its branch and leaf.
///
/// # Examples
///
/// ```
/// # use tui_tree_widget::get_identifier_without_leaf;
/// let (branch, leaf) = get_identifier_without_leaf(&[2, 4, 6]);
/// assert_eq!(branch, [2, 4]);
/// assert_eq!(leaf, Some(6));
/// assert_eq!(leaf, Some(&6));
///
/// let (branch, leaf) = get_identifier_without_leaf(&[2]);
/// assert_eq!(branch, []);
/// assert_eq!(leaf, Some(2));
/// assert_eq!(leaf, Some(&2));
///
/// let (branch, leaf) = get_identifier_without_leaf(&[]);
/// let (branch, leaf) = get_identifier_without_leaf::<usize>(&[]);
/// assert_eq!(branch, []);
/// assert_eq!(leaf, None);
/// ```
#[must_use]
pub const fn get_without_leaf(identifier: TreeIdentifier) -> (TreeIdentifier, Option<usize>) {
pub const fn get_without_leaf<Identifier>(
identifier: &[Identifier],
) -> (&[Identifier], Option<&Identifier>) {
match identifier {
[branch @ .., leaf] => (branch, Some(*leaf)),
[] => (&[] as &[usize], None),
[branch @ .., leaf] => (branch, Some(leaf)),
[] => (&[] as &[Identifier], None),
}
}
Loading

0 comments on commit b914819

Please sign in to comment.