use std::path::Path;
use crate::lint::LintStatus;
use crate::project::cargo::Package;
use crate::project::cargo::RustProject;
use crate::project::cargo::Workspace;
use crate::project::info::Visibility;
use crate::project::info::WorktreeHealth;
use crate::project::paths::AbsolutePath;
use crate::project::paths::DisplayPath;
use crate::project::project_fields::ProjectFields;
use crate::project::vendored_package::VendoredPackage;
#[derive(Clone)]
pub(crate) struct WorktreeGroup {
pub primary: RustProject,
pub linked: Vec<RustProject>,
}
impl WorktreeGroup {
pub const fn new(primary: RustProject, linked: Vec<RustProject>) -> Self {
Self { primary, linked }
}
pub fn primary_path(&self) -> &AbsolutePath { self.primary.path() }
pub fn derived_visibility(&self) -> Visibility {
if self.visible_entry_count() > 0 {
return Visibility::Visible;
}
if self.has_deleted_entry() {
return Visibility::Deleted;
}
Visibility::Dismissed
}
pub fn primary_worktree_health(&self) -> WorktreeHealth { self.primary.worktree_health() }
pub fn live_entry_count(&self) -> usize {
self.iter_visibility()
.filter(|v| !matches!(v, Visibility::Dismissed))
.count()
}
fn has_deleted_entry(&self) -> bool { self.iter_visibility().any(|v| v == Visibility::Deleted) }
pub fn visible_entry_count(&self) -> usize {
self.iter_visibility()
.filter(|v| *v == Visibility::Visible)
.count()
}
pub fn renders_as_group(&self) -> bool { self.live_entry_count() > 1 }
pub fn iter_entries(&self) -> impl Iterator<Item = &RustProject> + '_ {
std::iter::once(&self.primary).chain(self.linked.iter())
}
pub fn single_live(&self) -> Option<&RustProject> {
if self.live_entry_count() != 1 {
return None;
}
self.iter_entries()
.find(|p| !matches!(p.visibility(), Visibility::Dismissed))
}
pub fn single_live_workspace(&self) -> Option<&Workspace> {
match self.single_live()? {
RustProject::Workspace(ws) => Some(ws),
RustProject::Package(_) => None,
}
}
pub fn lint_rollup_status(&self) -> LintStatus {
let statuses: Vec<LintStatus> = self
.iter_entries()
.filter(|entry| entry.visibility() == Visibility::Visible)
.map(|entry| entry.rust_info().lint_runs.status())
.cloned()
.collect();
let running: Vec<LintStatus> = statuses
.iter()
.filter(|s| matches!(s, LintStatus::Running(_)))
.cloned()
.collect();
if !running.is_empty() {
return LintStatus::aggregate(running);
}
LintStatus::aggregate(statuses)
}
pub fn iter_paths(&self) -> impl Iterator<Item = &AbsolutePath> + '_ {
self.iter_entries().map(ProjectFields::path)
}
fn iter_visibility(&self) -> impl Iterator<Item = Visibility> + '_ {
self.iter_entries().map(ProjectFields::visibility)
}
pub fn entry(&self, wi: usize) -> Option<&RustProject> {
if wi == 0 {
Some(&self.primary)
} else {
self.linked.get(wi - 1)
}
}
pub fn member_ref(
&self,
worktree_index: usize,
group_index: usize,
member_index: usize,
) -> Option<&Package> {
let RustProject::Workspace(ws) = self.entry(worktree_index)? else {
return None;
};
ws.groups().get(group_index)?.members().get(member_index)
}
pub fn vendored_ref(
&self,
worktree_index: usize,
vendored_index: usize,
) -> Option<&VendoredPackage> {
self.entry(worktree_index)?
.rust_info()
.vendored()
.get(vendored_index)
}
pub fn member_vendored_ref(
&self,
worktree_index: usize,
group_index: usize,
member_index: usize,
vendored_index: usize,
) -> Option<&VendoredPackage> {
self.member_ref(worktree_index, group_index, member_index)?
.vendored()
.get(vendored_index)
}
pub fn worktree_display_path(&self, wi: usize) -> Option<DisplayPath> {
self.entry(wi).map(ProjectFields::display_path)
}
pub fn worktree_member_display_path(
&self,
wi: usize,
gi: usize,
mi: usize,
) -> Option<DisplayPath> {
let RustProject::Workspace(ws) = self.entry(wi)? else {
return None;
};
ws.groups()
.get(gi)?
.members()
.get(mi)
.map(ProjectFields::display_path)
}
pub fn worktree_vendored_display_path(&self, wi: usize, vi: usize) -> Option<DisplayPath> {
self.entry(wi)?
.rust_info()
.vendored()
.get(vi)
.map(ProjectFields::display_path)
}
pub fn worktree_abs_path(&self, wi: usize) -> Option<AbsolutePath> {
self.entry(wi).map(|p| p.path().clone())
}
pub fn worktree_member_abs_path(
&self,
wi: usize,
gi: usize,
mi: usize,
) -> Option<AbsolutePath> {
let RustProject::Workspace(ws) = self.entry(wi)? else {
return None;
};
ws.groups()
.get(gi)?
.members()
.get(mi)
.map(|p| p.path().clone())
}
pub fn worktree_vendored_abs_path(&self, wi: usize, vi: usize) -> Option<AbsolutePath> {
self.entry(wi)?
.rust_info()
.vendored()
.get(vi)
.map(|p| p.path().clone())
}
pub fn worktree_path_ref(&self, wi: usize) -> Option<&Path> {
self.entry(wi).map(|p| p.path().as_path())
}
pub fn worktree_member_path_ref(&self, wi: usize, gi: usize, mi: usize) -> Option<&Path> {
let RustProject::Workspace(ws) = self.entry(wi)? else {
return None;
};
ws.groups()
.get(gi)?
.members()
.get(mi)
.map(|p| p.path().as_path())
}
pub fn worktree_vendored_path_ref(&self, wi: usize, vi: usize) -> Option<&Path> {
self.entry(wi)?
.rust_info()
.vendored()
.get(vi)
.map(|p| p.path().as_path())
}
pub fn lint_status_for_worktree(&self, worktree_index: usize) -> LintStatus {
self.entry(worktree_index).map_or(LintStatus::NoLog, |p| {
p.rust_info().lint_runs.status().clone()
})
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use std::path::PathBuf;
use super::*;
fn pkg(path: &str) -> RustProject {
RustProject::Package(Package {
path: AbsolutePath::from(Path::new(path)),
..Package::default()
})
}
fn ws(path: &str) -> RustProject {
RustProject::Workspace(Workspace {
path: AbsolutePath::from(Path::new(path)),
..Workspace::default()
})
}
fn p(path: &str) -> PathBuf { crate::project::normalize_test_path(Path::new(path)) }
#[test]
fn iter_paths_yields_entries_in_canonical_order() {
for (group, expected) in [
(
WorktreeGroup::new(
pkg("/abs/main"),
vec![pkg("/abs/feat-a"), pkg("/abs/feat-b")],
),
vec![p("/abs/main"), p("/abs/feat-a"), p("/abs/feat-b")],
),
(
WorktreeGroup::new(ws("/abs/ws-main"), vec![ws("/abs/ws-feat")]),
vec![p("/abs/ws-main"), p("/abs/ws-feat")],
),
(
WorktreeGroup::new(pkg("/abs/solo"), Vec::new()),
vec![p("/abs/solo")],
),
(
WorktreeGroup::new(pkg("/abs/main"), vec![ws("/abs/api-fix")]),
vec![p("/abs/main"), p("/abs/api-fix")],
),
] {
let paths: Vec<PathBuf> = group
.iter_paths()
.map(|path| path.as_path().to_path_buf())
.collect();
assert_eq!(paths, expected);
}
}
}