use std::collections::HashSet;
use super::ProjectList;
use crate::project::AbsolutePath;
use crate::project::GitStatus;
use crate::project::MemberGroup;
use crate::project::RootItem;
use crate::project::RustProject;
use crate::project::Submodule;
use crate::project::VendoredPackage;
use crate::project::Visibility;
use crate::project::WorktreeGroup;
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum ExpandKey {
Node(usize),
Group(usize, usize),
Worktree(usize, usize),
WorktreeGroup(usize, usize, usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VisibleRow {
Root { node_index: usize },
GroupHeader {
node_index: usize,
group_index: usize,
},
Member {
node_index: usize,
group_index: usize,
member_index: usize,
},
MemberVendored {
node_index: usize,
group_index: usize,
member_index: usize,
vendored_index: usize,
},
Vendored {
node_index: usize,
vendored_index: usize,
},
WorktreeEntry {
node_index: usize,
worktree_index: usize,
},
WorktreeGroupHeader {
node_index: usize,
worktree_index: usize,
group_index: usize,
},
WorktreeMember {
node_index: usize,
worktree_index: usize,
group_index: usize,
member_index: usize,
},
WorktreeMemberVendored {
node_index: usize,
worktree_index: usize,
group_index: usize,
member_index: usize,
vendored_index: usize,
},
WorktreeVendored {
node_index: usize,
worktree_index: usize,
vendored_index: usize,
},
Submodule {
node_index: usize,
submodule_index: usize,
},
}
impl ProjectList {
pub fn compute_visible_rows(
&self,
expanded: &HashSet<ExpandKey>,
include_non_rust: bool,
) -> Vec<VisibleRow> {
let mut rows = Vec::new();
for (ni, entry) in self.iter().enumerate() {
let item = &entry.root_item;
if matches!(item.visibility(), Visibility::Dismissed) {
continue;
}
if !include_non_rust && !item.is_rust() {
continue;
}
rows.push(VisibleRow::Root { node_index: ni });
if !expanded.contains(&ExpandKey::Node(ni)) {
continue;
}
match item {
RootItem::Rust(RustProject::Workspace(ws)) => {
emit_groups(&mut rows, ni, ws.groups(), expanded);
emit_vendored_rows(&mut rows, ni, ws.vendored());
},
RootItem::Rust(RustProject::Package(pkg)) => {
emit_vendored_rows(&mut rows, ni, pkg.vendored());
},
RootItem::NonRust(_) => {},
RootItem::Worktrees(wtg) => {
if wtg.renders_as_group() {
emit_worktree_group(&mut rows, ni, wtg, expanded);
} else if let Some(entry) = wtg.single_live() {
if let RustProject::Workspace(ws) = entry {
emit_groups(&mut rows, ni, ws.groups(), expanded);
}
emit_vendored_rows(&mut rows, ni, entry.rust_info().vendored());
}
},
}
emit_submodule_rows(&mut rows, ni, item.submodules());
}
rows
}
}
fn emit_groups(
rows: &mut Vec<VisibleRow>,
ni: usize,
groups: &[MemberGroup],
expanded: &HashSet<ExpandKey>,
) {
for (gi, group) in groups.iter().enumerate() {
match group {
MemberGroup::Inline { members } => {
for (mi, member) in members.iter().enumerate() {
rows.push(VisibleRow::Member {
node_index: ni,
group_index: gi,
member_index: mi,
});
emit_member_vendored_rows(rows, ni, gi, mi, member.vendored());
}
},
MemberGroup::Named { members, .. } => {
rows.push(VisibleRow::GroupHeader {
node_index: ni,
group_index: gi,
});
if expanded.contains(&ExpandKey::Group(ni, gi)) {
for (mi, member) in members.iter().enumerate() {
rows.push(VisibleRow::Member {
node_index: ni,
group_index: gi,
member_index: mi,
});
emit_member_vendored_rows(rows, ni, gi, mi, member.vendored());
}
}
},
}
}
}
fn emit_member_vendored_rows(
rows: &mut Vec<VisibleRow>,
ni: usize,
gi: usize,
mi: usize,
vendored: &[VendoredPackage],
) {
for (vi, _) in vendored.iter().enumerate() {
rows.push(VisibleRow::MemberVendored {
node_index: ni,
group_index: gi,
member_index: mi,
vendored_index: vi,
});
}
}
fn emit_vendored_rows(rows: &mut Vec<VisibleRow>, ni: usize, vendored: &[VendoredPackage]) {
for (vi, _) in vendored.iter().enumerate() {
rows.push(VisibleRow::Vendored {
node_index: ni,
vendored_index: vi,
});
}
}
fn emit_submodule_rows(rows: &mut Vec<VisibleRow>, ni: usize, submodules: &[Submodule]) {
for (si, _) in submodules.iter().enumerate() {
rows.push(VisibleRow::Submodule {
node_index: ni,
submodule_index: si,
});
}
}
fn emit_worktree_group(
rows: &mut Vec<VisibleRow>,
ni: usize,
wtg: &WorktreeGroup,
expanded: &HashSet<ExpandKey>,
) {
let mut entries: Vec<_> = wtg
.iter_entries()
.enumerate()
.filter(|(_, entry)| !matches!(entry.visibility(), Visibility::Dismissed))
.map(|(wi, entry)| (entry.root_directory_name().into_string(), wi, entry))
.collect();
entries.sort_by(|(a_name, a_wi, _), (b_name, b_wi, _)| {
a_name.cmp(b_name).then_with(|| a_wi.cmp(b_wi))
});
for (_, wi, entry) in entries {
rows.push(VisibleRow::WorktreeEntry {
node_index: ni,
worktree_index: wi,
});
if let RustProject::Workspace(ws) = entry
&& ws.has_members()
&& expanded.contains(&ExpandKey::Worktree(ni, wi))
{
emit_worktree_children(rows, ni, wi, ws.groups(), ws.vendored(), expanded);
}
}
}
fn emit_worktree_children(
rows: &mut Vec<VisibleRow>,
ni: usize,
wi: usize,
groups: &[MemberGroup],
vendored: &[VendoredPackage],
expanded: &HashSet<ExpandKey>,
) {
for (gi, group) in groups.iter().enumerate() {
match group {
MemberGroup::Inline { members } => {
for (mi, member) in members.iter().enumerate() {
rows.push(VisibleRow::WorktreeMember {
node_index: ni,
worktree_index: wi,
group_index: gi,
member_index: mi,
});
emit_worktree_member_vendored_rows(rows, ni, wi, gi, mi, member.vendored());
}
},
MemberGroup::Named { members, .. } => {
rows.push(VisibleRow::WorktreeGroupHeader {
node_index: ni,
worktree_index: wi,
group_index: gi,
});
if expanded.contains(&ExpandKey::WorktreeGroup(ni, wi, gi)) {
for (mi, member) in members.iter().enumerate() {
rows.push(VisibleRow::WorktreeMember {
node_index: ni,
worktree_index: wi,
group_index: gi,
member_index: mi,
});
emit_worktree_member_vendored_rows(rows, ni, wi, gi, mi, member.vendored());
}
}
},
}
}
for (vi, _) in vendored.iter().enumerate() {
rows.push(VisibleRow::WorktreeVendored {
node_index: ni,
worktree_index: wi,
vendored_index: vi,
});
}
}
fn emit_worktree_member_vendored_rows(
rows: &mut Vec<VisibleRow>,
ni: usize,
wi: usize,
gi: usize,
mi: usize,
vendored: &[VendoredPackage],
) {
for (vi, _) in vendored.iter().enumerate() {
rows.push(VisibleRow::WorktreeMemberVendored {
node_index: ni,
worktree_index: wi,
group_index: gi,
member_index: mi,
vendored_index: vi,
});
}
}
pub(super) fn worst_git_status(
states: impl Iterator<Item = Option<GitStatus>>,
) -> Option<GitStatus> {
const fn severity(state: GitStatus) -> u8 {
match state {
GitStatus::Modified => 4,
GitStatus::Untracked => 3,
GitStatus::Clean => 2,
GitStatus::Ignored => 1,
}
}
states.flatten().max_by_key(|s| severity(*s))
}
#[derive(Clone)]
pub enum LegacyRootExpansionKind {
HadChildren,
Leaf,
}
impl LegacyRootExpansionKind {
pub(super) const fn from_had_children(had_children: bool) -> Self {
if had_children {
Self::HadChildren
} else {
Self::Leaf
}
}
pub(super) const fn had_children(&self) -> bool { matches!(self, Self::HadChildren) }
}
#[derive(Clone)]
pub struct LegacyRootExpansion {
pub(super) root_path: AbsolutePath,
pub(super) old_node_index: usize,
pub(super) expansion_kind: LegacyRootExpansionKind,
pub(super) named_groups: Vec<usize>,
}
impl VisibleRow {
pub(super) const fn collapse_anchor(self) -> Self {
match self {
Self::GroupHeader { node_index, .. }
| Self::Member { node_index, .. }
| Self::MemberVendored { node_index, .. }
| Self::Vendored { node_index, .. }
| Self::Submodule { node_index, .. } => Self::Root { node_index },
Self::Root { .. } | Self::WorktreeEntry { .. } => self,
Self::WorktreeGroupHeader {
node_index,
worktree_index,
..
}
| Self::WorktreeMember {
node_index,
worktree_index,
..
}
| Self::WorktreeMemberVendored {
node_index,
worktree_index,
..
}
| Self::WorktreeVendored {
node_index,
worktree_index,
..
} => Self::WorktreeEntry {
node_index,
worktree_index,
},
}
}
}