use std::path::PathBuf;
pub struct TreeState {
selected: Option<usize>,
}
impl TreeState {
pub fn new() -> Self {
Self { selected: None }
}
pub fn select(&mut self, index: Option<usize>) {
self.selected = index;
}
pub fn selected(&self) -> Option<usize> {
self.selected
}
}
#[derive(Clone)]
pub struct RepoInfo {
pub path: PathBuf,
pub display_name: String,
pub is_clean: bool,
pub modification_time: Option<std::time::SystemTime>,
pub size_bytes: Option<u64>,
}
#[derive(Clone)]
pub struct TreeNode {
pub name: String,
pub repo_info: Option<RepoInfo>,
pub children: Vec<TreeNode>,
pub expanded: bool,
}
impl TreeNode {
pub fn new_repo(repo: RepoInfo) -> Self {
let name = repo
.display_name
.split('/')
.next_back()
.unwrap_or(&repo.display_name)
.to_string();
Self {
name,
repo_info: Some(repo),
children: Vec::new(),
expanded: false,
}
}
pub fn new_directory(name: String) -> Self {
Self {
name,
repo_info: None,
children: Vec::new(),
expanded: true,
}
}
pub fn flatten(
&self,
depth: usize,
index_path: Vec<usize>,
parent_path: &str,
) -> Vec<(TreeNode, usize, Vec<usize>, String)> {
let full_path = if parent_path.is_empty() {
if let Some(ref repo) = self.repo_info {
repo.display_name.clone()
} else {
self.name.clone()
}
} else if let Some(ref repo) = self.repo_info {
repo.display_name.clone()
} else {
format!("{}/{}", parent_path, self.name)
};
let mut result = vec![(self.clone(), depth, index_path.clone(), full_path.clone())];
if self.expanded {
for (i, child) in self.children.iter().enumerate() {
let mut child_index_path = index_path.clone();
child_index_path.push(i);
result.extend(child.flatten(depth + 1, child_index_path, &full_path));
}
}
result
}
pub fn toggle_expand(&mut self) {
if !self.children.is_empty() {
self.expanded = !self.expanded;
}
}
pub fn collect_repo_paths(&self) -> Vec<String> {
let mut paths = Vec::new();
if let Some(ref repo) = self.repo_info {
paths.push(repo.display_name.clone());
}
for child in &self.children {
paths.extend(child.collect_repo_paths());
}
paths
}
pub fn count_repos(&self) -> usize {
let mut count = if self.repo_info.is_some() { 1 } else { 0 };
for child in &self.children {
count += child.count_repos();
}
count
}
}
pub fn build_tree(mut repos: Vec<RepoInfo>) -> Vec<TreeNode> {
repos.sort_by(|a, b| {
match (a.modification_time, b.modification_time) {
(Some(a_time), Some(b_time)) => b_time.cmp(&a_time), (Some(_), None) => std::cmp::Ordering::Less, (None, Some(_)) => std::cmp::Ordering::Greater, (None, None) => a.display_name.cmp(&b.display_name), }
});
let mut root_nodes: Vec<TreeNode> = Vec::new();
for repo in repos {
let parts: Vec<&str> = repo.display_name.split('/').collect();
if parts.is_empty() {
continue;
}
let mut current_level = &mut root_nodes;
for (i, part) in parts.iter().enumerate() {
let is_last = i == parts.len() - 1;
let node_idx = current_level.iter().position(|n| n.name == *part);
if let Some(idx) = node_idx {
if is_last {
current_level[idx].repo_info = Some(repo.clone());
}
current_level = &mut current_level[idx].children;
} else {
let new_node = if is_last {
TreeNode::new_repo(repo.clone())
} else {
TreeNode::new_directory(part.to_string())
};
current_level.push(new_node);
let new_idx = current_level.len() - 1;
current_level = &mut current_level[new_idx].children;
}
}
}
root_nodes
}
pub fn build_library_tree(
library_repos: Vec<RepoInfo>,
workspace_repos: &[RepoInfo],
) -> Vec<TreeNode> {
let workspace_paths: std::collections::HashSet<_> = workspace_repos
.iter()
.map(|r| r.display_name.as_str())
.collect();
let filtered_repos: Vec<RepoInfo> = library_repos
.into_iter()
.filter(|repo| !workspace_paths.contains(repo.display_name.as_str()))
.collect();
build_tree(filtered_repos)
}
pub fn flatten_trees(trees: &[TreeNode]) -> Vec<(TreeNode, usize, Vec<usize>, String)> {
let mut result = Vec::new();
for (i, tree) in trees.iter().enumerate() {
result.extend(tree.flatten(0, vec![i], ""));
}
result
}
pub fn count_repos_in_trees(trees: &[TreeNode]) -> usize {
trees.iter().map(|t| t.count_repos()).sum()
}
pub fn toggle_node_at_path(trees: &mut [TreeNode], path: &[usize]) {
if path.is_empty() {
return;
}
if path.len() == 1 {
if let Some(node) = trees.get_mut(path[0]) {
node.toggle_expand();
}
} else if let Some(node) = trees.get_mut(path[0]) {
toggle_node_at_path_impl(node, &path[1..]);
}
}
fn toggle_node_at_path_impl(node: &mut TreeNode, path: &[usize]) {
if path.is_empty() {
node.toggle_expand();
} else if path.len() == 1 {
if let Some(child) = node.children.get_mut(path[0]) {
child.toggle_expand();
}
} else if let Some(child) = node.children.get_mut(path[0]) {
toggle_node_at_path_impl(child, &path[1..]);
}
}