use std::path::{Path, PathBuf};
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct TreeItemInfo {
indent: u8,
visible: bool,
folded: Option<PathBuf>,
full_path: PathBuf,
}
impl TreeItemInfo {
pub const fn new(indent: u8, full_path: PathBuf) -> Self {
Self {
indent,
visible: true,
folded: None,
full_path,
}
}
pub const fn is_visible(&self) -> bool {
self.visible
}
pub fn full_path_str(&self) -> &str {
self.full_path.to_str().unwrap_or_default()
}
pub fn full_path(&self) -> &Path {
self.full_path.as_path()
}
pub fn path_str(&self) -> &str {
self.path().as_os_str().to_str().unwrap_or_default()
}
pub fn path(&self) -> &Path {
self.folded.as_ref().map_or_else(
|| {
Path::new(
self.full_path
.components()
.next_back()
.and_then(|c| c.as_os_str().to_str())
.unwrap_or_default(), )
},
PathBuf::as_path,
)
}
pub const fn indent(&self) -> u8 {
self.indent
}
pub const fn unindent(&mut self) { self.indent = self.indent.saturating_sub(1);
}
pub const fn set_visible(&mut self, visible: bool) { self.visible = visible;
}
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct PathCollapsed(pub bool);
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum FileTreeItemKind {
Path(PathCollapsed),
File,
}
impl FileTreeItemKind {
pub const fn is_path(&self) -> bool {
matches!(self, Self::Path(_))
}
pub const fn is_path_collapsed(&self) -> bool {
match self {
Self::Path(collapsed) => collapsed.0,
Self::File => false,
}
}
}
#[derive(Debug, Clone)]
pub struct FileTreeItem {
info: TreeItemInfo,
kind: FileTreeItemKind,
}
impl FileTreeItem {
pub fn new_file(path: &Path) -> Result<Self> {
let item_path = PathBuf::from(path);
let indent = u8::try_from(
item_path.ancestors().count().saturating_sub(2),
)?;
Ok(Self {
info: TreeItemInfo::new(indent, item_path),
kind: FileTreeItemKind::File,
})
}
pub fn new_path(path: &Path, collapsed: bool) -> Result<Self> {
let indent =
u8::try_from(path.ancestors().count().saturating_sub(2))?;
Ok(Self {
info: TreeItemInfo::new(indent, path.to_owned()),
kind: FileTreeItemKind::Path(PathCollapsed(collapsed)),
})
}
pub fn fold(&mut self, next: Self) {
if let Some(folded) = self.info.folded.as_mut() {
*folded = folded.join(next.info.path());
} else {
self.info.folded =
Some(self.info.path().join(next.info.path()));
}
self.info.full_path = next.info.full_path;
}
pub const fn info(&self) -> &TreeItemInfo {
&self.info
}
pub const fn info_mut(&mut self) -> &mut TreeItemInfo { &mut self.info
}
pub const fn kind(&self) -> &FileTreeItemKind {
&self.kind
}
pub fn collapse_path(&mut self) {
assert!(self.kind.is_path());
self.kind = FileTreeItemKind::Path(PathCollapsed(true));
}
pub fn expand_path(&mut self) {
assert!(self.kind.is_path());
self.kind = FileTreeItemKind::Path(PathCollapsed(false));
}
pub const fn hide(&mut self) { self.info.visible = false;
}
pub const fn show(&mut self) { self.info.visible = true;
}
}
impl Eq for FileTreeItem {}
impl PartialEq for FileTreeItem {
fn eq(&self, other: &Self) -> bool {
self.info.full_path.eq(&other.info.full_path)
}
}
impl PartialOrd for FileTreeItem {
fn partial_cmp(
&self,
other: &Self,
) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileTreeItem {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.info.path().cmp(other.info.path())
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_smoke() {
let mut a =
FileTreeItem::new_path(Path::new("a"), false).unwrap();
assert_eq!(a.info.full_path_str(), "a");
assert_eq!(a.info.path_str(), "a");
let b =
FileTreeItem::new_path(Path::new("a/b"), false).unwrap();
a.fold(b);
assert_eq!(a.info.full_path_str(), "a/b");
assert_eq!(
&a.info.folded.as_ref().unwrap(),
&Path::new("a/b")
);
assert_eq!(a.info.path(), Path::new("a/b"));
}
}