use std::sync::Arc;
use crate::config::Scope;
use crate::model::join::BeadKey;
use crate::model::snapshot::{Counts, Snapshot, Tree};
use crate::model::tree::Link;
use crate::view::lines::{
first_copy, marker, notes_of, prefix, root_key, 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) -> Vec<Line> {
Layout {
snapshot,
facts,
folds,
}
.draw()
}
pub(super) fn group_drawn(snapshot: &Snapshot, kind: GroupKind, project: Option<&str>) -> bool {
group_of(snapshot, kind, project).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>,
) -> Option<BeadKey> {
match kind {
GroupKind::HiddenTrees => {
let tree = hidden_trees(snapshot, project).into_iter().next()?;
Some(BeadKey {
project: tree.project.clone(),
id: tree.beads.first()?.id.clone(),
})
}
_ => None,
}
}
pub(super) fn trees_drawn(snapshot: &Snapshot) -> Vec<&Tree> {
let mut drawn = Vec::new();
for project in &snapshot.projects {
if !project_drawn(snapshot, project) {
continue;
}
drawn.extend(
snapshot
.trees
.iter()
.filter(|tree| tree.project == *project)
.map(Arc::as_ref),
);
drawn.extend(hidden_trees(snapshot, Some(project)));
}
drawn
}
fn group_of(snapshot: &Snapshot, kind: GroupKind, project: Option<&str>) -> Option<Group> {
let (count, with_findings) = 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(),
)
}
_ => (group_items(snapshot, kind, project).len(), 0),
};
(count > 0).then_some(Group {
kind,
project: project.map(str::to_string),
count,
with_findings,
})
}
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,
}
impl Layout<'_> {
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> = 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_map(|kind| group_of(self.snapshot, kind, Some(&project)))
.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,
}
.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 => {
let hidden = self.hidden_trees(project.as_deref());
let count = hidden.len();
for (n, tree) in hidden.into_iter().enumerate() {
TreeLayout {
folds: self.folds,
tree,
facts: self.facts.tree(&root_key(tree)),
rests_shut: true,
}
.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 hidden_trees(&self, project: Option<&str>) -> Vec<&Tree> {
hidden_trees(self.snapshot, project)
}
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) 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,
}
impl TreeLayout<'_> {
fn draw(&self, trunk: &mut Vec<bool>, last: bool, lines: &mut Vec<Line>) {
let root = Place::root(root_key(self.tree));
let depth = trunk.len() as u16 + 1;
let Some(node) = self.tree.beads.first() 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(0, &[]);
let bead = self.facts.bead(self.tree, 0, &[]);
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,
&self.tree.root,
bead.progress,
shut_over(bead.beneath, first_copy(self.tree, 0, &[]), 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, &[0], 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.facts.run_size(self.tree, &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,
&self.tree.root,
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().map(Child::Node).collect();
if !elided.is_empty() {
entries.push(Child::Elided(elided));
}
entries
}
}
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 => 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),
}
}