use std::collections::BTreeSet;
use std::sync::Arc;
use crate::config::Scope;
use crate::model::join::BeadKey;
use crate::model::snapshot::{Counts, Node, Snapshot, Tree};
use crate::model::tree::Link;
use crate::view::lines::{
first_copy, marker, notes_of, prefix, root_key, run_size, way_below, Content, Group, GroupKind,
Item, Line, Note, Place, ProjectLine, Unread, INDENT,
};
use crate::view::row;
use super::facts::{Facts, TreeFacts};
use super::handle::{item_key, Folds, Handle, ItemKey};
enum Child<'a> {
Note(Note),
Node(&'a Link),
Elided(Vec<&'a Link>),
}
fn shut_over(beneath: Counts, first: bool, folded: Option<bool>) -> Option<Counts> {
(folded == Some(false) && first).then_some(beneath)
}
pub(super) fn draw(
snapshot: &Snapshot,
facts: &Facts,
folds: &Folds,
rooted: Option<&Rooted>,
) -> Vec<Line> {
Layout {
snapshot,
facts,
folds,
rooted,
}
.draw()
}
pub(super) struct Rooted {
pub(super) place: Place,
pub(super) way: Vec<usize>,
}
pub(super) fn group_drawn(
snapshot: &Snapshot,
kind: GroupKind,
project: Option<&str>,
rooted: Option<&Rooted>,
) -> bool {
group_of(snapshot, kind, project, rooted).is_some()
}
pub(super) fn project_drawn(snapshot: &Snapshot, project: &str) -> bool {
snapshot.trees.iter().any(|tree| tree.project == project)
|| snapshot
.hidden_trees
.iter()
.any(|hidden| hidden.project == project)
|| snapshot
.unattributed
.iter()
.any(|pane| pane.project == project)
|| !snapshot.read_at.contains_key(project)
}
fn hidden_trees<'a>(snapshot: &'a Snapshot, project: Option<&str>) -> Vec<&'a Tree> {
snapshot
.hidden_trees
.iter()
.filter(|hidden| Some(hidden.project.as_str()) == project)
.filter_map(|hidden| {
snapshot.tree(&BeadKey {
project: hidden.project.clone(),
id: hidden.root.clone(),
})
})
.collect()
}
pub(super) fn first_bead_of(
snapshot: &Snapshot,
kind: GroupKind,
project: Option<&str>,
rooted: Option<&Rooted>,
) -> Option<BeadKey> {
let roots = match kind {
GroupKind::HiddenTrees => hidden_trees(snapshot, project),
GroupKind::OutOfTheWay => out_of_the_way(snapshot, project, rooted)
.into_iter()
.map(|root| root.tree)
.collect(),
_ => return None,
};
roots.into_iter().find_map(|tree| {
Some(BeadKey {
project: tree.project.clone(),
id: tree.beads.first()?.id.clone(),
})
})
}
pub(super) fn walked<'a>(
snapshot: &'a Snapshot,
rooted: Option<&Rooted>,
) -> Vec<(&'a Tree, Vec<usize>)> {
let mut drawn = Vec::new();
for project in &snapshot.projects {
if !project_drawn(snapshot, project) {
continue;
}
let Some(rooted) = rooted else {
drawn.extend(
snapshot
.trees
.iter()
.filter(|tree| tree.project == *project)
.map(|tree| (Arc::as_ref(tree), vec![0])),
);
drawn.extend(
hidden_trees(snapshot, Some(project))
.into_iter()
.map(|tree| (tree, vec![0])),
);
continue;
};
if rooted.place.tree.project == *project {
drawn.extend(
snapshot
.tree(&rooted.place.tree)
.map(|tree| (tree, rooted.way.clone())),
);
}
drawn.extend(
out_of_the_way(snapshot, Some(project), Some(rooted))
.into_iter()
.map(|root| (root.tree, vec![0])),
);
}
drawn
}
fn group_of(
snapshot: &Snapshot,
kind: GroupKind,
project: Option<&str>,
rooted: Option<&Rooted>,
) -> Option<Group> {
let (count, with_findings, held) = match kind {
GroupKind::HiddenTrees => {
let hidden = snapshot
.hidden_trees
.iter()
.filter(|hidden| Some(hidden.project.as_str()) == project);
(
hidden.clone().count(),
hidden.filter(|hidden| hidden.findings).count(),
None,
)
}
GroupKind::OutOfTheWay => {
let held = out_of_the_way(snapshot, project, rooted);
let counts = Counts::over(held.iter().flat_map(Behind::beads));
(held.len(), 0, Some(counts))
}
_ => (group_items(snapshot, kind, project).len(), 0, None),
};
(count > 0).then_some(Group {
kind,
project: project.map(str::to_string),
count,
with_findings,
held,
})
}
struct Behind<'a> {
tree: &'a Tree,
without: Option<usize>,
}
impl<'a> Behind<'a> {
fn beads(&self) -> Vec<&'a Node> {
reached(self.tree, [0], self.without)
.into_iter()
.filter_map(|at| self.tree.beads.get(at))
.collect()
}
}
fn reached(
tree: &Tree,
from: impl IntoIterator<Item = usize>,
without: Option<usize>,
) -> BTreeSet<usize> {
let mut walked = BTreeSet::new();
let mut left: Vec<usize> = from.into_iter().collect();
while let Some(at) = left.pop() {
if Some(at) == without || !walked.insert(at) {
continue;
}
left.extend(
tree.children
.get(at)
.into_iter()
.flatten()
.map(|link| link.bead),
);
}
walked
}
fn out_of_the_way<'a>(
snapshot: &'a Snapshot,
project: Option<&str>,
rooted: Option<&Rooted>,
) -> Vec<Behind<'a>> {
let Some(rooted) = rooted else {
return Vec::new();
};
let (focused, above) = rooted.way.split_last().expect("a way down ends somewhere");
snapshot
.collected
.iter()
.map(Arc::as_ref)
.filter(|tree| Some(tree.project.as_str()) == project)
.filter_map(|tree| {
if root_key(tree) != rooted.place.tree {
return Some(Behind {
tree,
without: None,
});
}
(!above.is_empty()).then_some(Behind {
tree,
without: Some(*focused),
})
})
.collect()
}
pub(super) fn every_group(
snapshot: &Snapshot,
) -> impl Iterator<Item = (GroupKind, Option<String>)> + '_ {
snapshot
.projects
.iter()
.flat_map(|project| {
GroupKind::UNDER_A_PROJECT
.into_iter()
.map(move |kind| (kind, Some(project.clone())))
})
.chain(
GroupKind::BELOW_THE_TREES
.into_iter()
.map(|kind| (kind, None)),
)
}
pub(super) fn group_holding(snapshot: &Snapshot, key: &ItemKey) -> Option<Handle> {
every_group(snapshot)
.find(|(kind, project)| {
group_items(snapshot, *kind, project.as_deref())
.iter()
.any(|item| item_key(item).as_ref() == Some(key))
})
.map(|(kind, project)| Handle::Group(kind, project))
}
struct Layout<'a> {
snapshot: &'a Snapshot,
facts: &'a Facts,
folds: &'a Folds,
rooted: Option<&'a Rooted>,
}
impl<'a> Layout<'a> {
fn draw(&self) -> Vec<Line> {
let mut lines = Vec::new();
for project in &self.snapshot.projects {
if project_drawn(self.snapshot, project) {
self.draw_project(project, &mut lines);
}
}
self.draw_groups(&mut lines);
if lines.is_empty() {
lines.push(nothing_to_draw());
}
self.say_what_the_directory_chose(&mut lines);
lines
}
fn draw_project(&self, project: &str, lines: &mut Vec<Line>) {
let project = project.to_string();
let open = self.folds.expanded(&Handle::Project(project.clone()), true);
lines.push(Line {
prefix: marker(open).to_string(),
depth: 0,
folded: Some(open),
place: None,
content: Content::Project(ProjectLine {
every_root_read: self.snapshot.every_root_read(&project),
counts: self.facts.project(&project),
project: project.clone(),
}),
});
if !open {
return;
}
let trees: Vec<&Tree> = match self.rooted {
Some(rooted) if rooted.place.tree.project == project => {
self.snapshot.tree(&rooted.place.tree).into_iter().collect()
}
Some(_) => Vec::new(),
None => self
.snapshot
.trees
.iter()
.filter(|tree| tree.project == project)
.map(Arc::as_ref)
.collect(),
};
let groups: Vec<Group> = GroupKind::UNDER_A_PROJECT
.into_iter()
.filter(|kind| self.rooted.is_none() || *kind != GroupKind::HiddenTrees)
.filter_map(|kind| group_of(self.snapshot, kind, Some(&project), self.rooted))
.collect();
let mut entries = trees.len() + groups.len();
let mut trunk = Vec::new();
for tree in trees {
entries -= 1;
TreeLayout {
folds: self.folds,
tree,
facts: self.facts.tree(&root_key(tree)),
rests_shut: false,
rooted: self.rooted,
without: None,
}
.draw(&mut trunk, entries == 0, lines);
}
for group in groups {
entries -= 1;
self.draw_group(group, &mut trunk, entries == 0, lines);
}
}
fn draw_group(&self, group: Group, trunk: &mut Vec<bool>, last: bool, lines: &mut Vec<Line>) {
let open = self.folds.expanded(
&Handle::Group(group.kind, group.project.clone()),
group.kind.live(),
);
let prefix = if trunk.is_empty() && group.project.is_none() {
marker(open).to_string()
} else {
prefix(trunk, last, !open, None)
};
let depth = trunk.len() as u16 + u16::from(group.project.is_some());
let kind = group.kind;
let project = group.project.clone();
lines.push(Line {
prefix,
depth,
folded: Some(open),
place: None,
content: Content::Group(group),
});
if !open {
return;
}
if project.is_some() {
trunk.push(!last);
}
match kind {
GroupKind::HiddenTrees | GroupKind::OutOfTheWay => {
let roots = self.roots_in(kind, project.as_deref());
let count = roots.len();
for (n, root) in roots.into_iter().enumerate() {
TreeLayout {
folds: self.folds,
tree: root.tree,
facts: self.facts.tree(&root_key(root.tree)),
rests_shut: true,
rooted: None,
without: root.without,
}
.draw(trunk, n + 1 == count, lines);
}
}
_ => self.draw_items(
group_items(self.snapshot, kind, project.as_deref()),
trunk,
lines,
),
}
if project.is_some() {
trunk.pop();
}
}
fn roots_in(&self, kind: GroupKind, project: Option<&str>) -> Vec<Behind<'a>> {
match kind {
GroupKind::OutOfTheWay => out_of_the_way(self.snapshot, project, self.rooted),
_ => hidden_trees(self.snapshot, project)
.into_iter()
.map(|tree| Behind {
tree,
without: None,
})
.collect(),
}
}
fn draw_items(&self, items: Vec<Item>, trunk: &[bool], lines: &mut Vec<Line>) {
let count = items.len();
let depth = trunk.len() as u16 + 1;
for (n, item) in items.into_iter().enumerate() {
lines.push(Line {
prefix: prefix(trunk, n + 1 == count, false, None),
depth,
folded: None,
place: None,
content: Content::Item(item),
});
}
}
fn draw_groups(&self, lines: &mut Vec<Line>) {
for kind in GroupKind::BELOW_THE_TREES {
let Some(group) = group_of(self.snapshot, kind, None, self.rooted) else {
continue;
};
self.draw_group(group, &mut Vec::new(), true, lines);
}
}
}
struct TreeLayout<'a> {
folds: &'a Folds,
tree: &'a Tree,
facts: &'a TreeFacts,
rests_shut: bool,
rooted: Option<&'a Rooted>,
without: Option<usize>,
}
impl TreeLayout<'_> {
fn draw(&self, trunk: &mut Vec<bool>, last: bool, lines: &mut Vec<Line>) {
let (root, way) = match self.rooted {
Some(rooted) => (rooted.place.clone(), rooted.way.clone()),
None => (Place::root(root_key(self.tree)), vec![0]),
};
let (at, above) = way.split_last().expect("a way down ends somewhere");
let at = *at;
let depth = trunk.len() as u16 + 1;
let Some(node) = self.tree.beads.get(at) else {
lines.push(Line {
prefix: prefix(trunk, last, false, None),
depth,
folded: None,
place: Some(root),
content: Content::Unread(Unread {
root: self.tree.root.clone(),
tracker: self.tree.tracker.clone(),
}),
});
return;
};
let kids = self.children_entries(at, above);
let bead = self.facts.bead(self.tree, at, above);
let open = !kids.is_empty()
&& self.folds.expanded(
&Handle::Bead(root.clone()),
!self.rests_shut && bead.opens_a_fold,
);
let folded = (!kids.is_empty()).then_some(open);
lines.push(Line {
prefix: prefix(trunk, last, !kids.is_empty() && !open, None),
depth,
folded,
place: Some(root.clone()),
content: Content::Bead(row::cells(
node,
None,
bead.progress,
shut_over(bead.beneath, first_copy(self.tree, at, above), folded),
)),
});
let mut entries: Vec<Child> = notes_of(self.tree).into_iter().map(Child::Note).collect();
if open {
entries.extend(kids);
}
trunk.push(!last);
self.draw_children(entries, &root, &way, trunk, lines);
trunk.pop();
}
fn draw_children(
&self,
entries: Vec<Child>,
parent: &Place,
above: &[usize],
trunk: &mut Vec<bool>,
lines: &mut Vec<Line>,
) {
let count = entries.len();
let depth = trunk.len() as u16 + 1;
for (n, entry) in entries.into_iter().enumerate() {
let last = n + 1 == count;
match entry {
Child::Note(note) => lines.push(Line {
prefix: prefix(trunk, last, false, None),
depth,
folded: None,
place: None,
content: Content::Note(note),
}),
Child::Elided(members) => {
let open = self.folds.expanded(&Handle::Elided(parent.clone()), false);
lines.push(Line {
prefix: prefix(trunk, last, !open, None),
depth,
folded: Some(open),
place: None,
content: Content::Elided {
count: self.run_size(&members, above),
under: parent.clone(),
},
});
if open {
trunk.push(!last);
let entries = members.into_iter().map(Child::Node).collect();
self.draw_children(entries, parent, above, trunk, lines);
trunk.pop();
}
}
Child::Node(link) => {
let at = link.bead;
let node = &self.tree.beads[at];
let place = parent.step_to(BeadKey {
project: self.tree.project.clone(),
id: node.id.clone(),
});
let kids = self.children_entries(at, above);
let bead = self.facts.bead(self.tree, at, above);
let first = first_copy(self.tree, at, above);
let open = !kids.is_empty()
&& self
.folds
.expanded(&Handle::Bead(place.clone()), first && bead.opens_a_fold);
let folded = (!kids.is_empty()).then_some(open);
lines.push(Line {
prefix: prefix(trunk, last, !kids.is_empty() && !open, Some(&link.edge)),
depth,
folded,
place: Some(place.clone()),
content: Content::Bead(row::cells(
node,
Some(&parent.key().id),
bead.progress,
shut_over(bead.beneath, first, folded),
)),
});
if open {
trunk.push(!last);
let below = way_below(above, at);
self.draw_children(kids, &place, &below, trunk, lines);
trunk.pop();
}
}
}
}
}
fn children_entries<'a>(&'a self, at: usize, above: &[usize]) -> Vec<Child<'a>> {
let (drawn, elided) = self.facts.split(self.tree, at, above);
let mut entries: Vec<Child> = drawn
.into_iter()
.filter(|link| self.draws(link))
.map(Child::Node)
.collect();
let elided: Vec<&Link> = elided.into_iter().filter(|link| self.draws(link)).collect();
if !elided.is_empty() {
entries.push(Child::Elided(elided));
}
entries
}
fn draws(&self, link: &Link) -> bool {
Some(link.bead) != self.without
}
fn run_size(&self, members: &[&Link], above: &[usize]) -> usize {
match self.without {
Some(elsewhere) => {
let above: Vec<usize> = above.iter().copied().chain([elsewhere]).collect();
run_size(self.tree, members, &above)
}
None => self.facts.run_size(self.tree, members, above),
}
}
}
impl Layout<'_> {
fn say_what_the_directory_chose(&self, lines: &mut Vec<Line>) {
if let Scope::Directory { project, .. } = &self.snapshot.scope {
lines.push(Line {
prefix: INDENT.to_string(),
depth: 0,
folded: None,
place: None,
content: Content::Scoped {
project: project.clone(),
},
});
}
}
}
fn group_items(snapshot: &Snapshot, kind: GroupKind, project: Option<&str>) -> Vec<Item> {
match kind {
GroupKind::FailedProjects => snapshot
.failed_projects
.iter()
.cloned()
.map(Item::Failed)
.collect(),
GroupKind::Conflicts => snapshot
.conflicts
.iter()
.cloned()
.map(Item::Conflict)
.collect(),
GroupKind::HiddenTrees | GroupKind::OutOfTheWay => Vec::new(),
GroupKind::Unattributed => snapshot
.unattributed
.iter()
.filter(|pane| Some(pane.project.as_str()) == project)
.cloned()
.map(Item::Loose)
.collect(),
GroupKind::Unconfigured => snapshot
.unconfigured
.iter()
.cloned()
.map(Item::Unconfigured)
.collect(),
}
}
fn nothing_to_draw() -> Line {
Line {
prefix: String::new(),
depth: 0,
folded: None,
place: None,
content: Content::Note(Note::NoRoots),
}
}