use std::collections::BTreeSet;
use crate::model::join::{BeadKey, Conflict};
use crate::model::snapshot::{
Counts, FailedProject, LoosePane, Node, TrackerState, Tree, UnconfiguredPane,
};
use crate::model::tree::{self, Link};
use crate::model::types::Edge;
use crate::view::row::{Progress, Row};
const MANY: usize = 3;
pub(crate) const OPEN: &str = "▾ ";
pub(crate) const SHUT: &str = "▸ ";
pub(crate) const INDENT: &str = " ";
const BRANCH: char = '├';
const LAST: char = '└';
const ARM: char = '─';
const BLOCKS_ARM: char = '┄';
const SHUT_IN_THE_ARM: char = '▸';
const TRUNK: &str = "│ ";
const GAP: &str = " ";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Place {
pub tree: BeadKey,
pub steps: Vec<BeadKey>,
}
impl Place {
pub(crate) fn root(tree: BeadKey) -> Self {
Self {
tree,
steps: Vec::new(),
}
}
pub(crate) fn step_to(&self, key: BeadKey) -> Self {
let mut stepped = self.clone();
stepped.steps.push(key);
stepped
}
pub(crate) fn key(&self) -> &BeadKey {
self.steps.last().unwrap_or(&self.tree)
}
pub(crate) fn forebears(&self) -> impl Iterator<Item = Place> + '_ {
(0..self.steps.len()).rev().map(|kept| Self {
tree: self.tree.clone(),
steps: self.steps[..kept].to_vec(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Line {
pub prefix: String,
pub depth: u16,
pub folded: Option<bool>,
pub place: Option<Place>,
pub content: Content,
}
impl Line {
pub fn bead(&self) -> Option<&BeadKey> {
self.place.as_ref().map(Place::key)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Content {
Project(ProjectLine),
Bead(Row),
Unread(Unread),
Elided {
count: usize,
under: Place,
},
Note(Note),
Group(Group),
Item(Item),
Scoped {
project: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectLine {
pub project: String,
pub counts: Counts,
pub every_root_read: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Unread {
pub root: String,
pub tracker: TrackerState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Note {
Dangling(usize),
Cycle(usize),
NoRoots,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Group {
pub kind: GroupKind,
pub project: Option<String>,
pub count: usize,
pub held: Option<Counts>,
pub with_findings: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum GroupKind {
FailedProjects,
Unconfigured,
Conflicts,
OutOfTheWay,
HiddenTrees,
Unattributed,
}
impl GroupKind {
pub const BELOW_THE_TREES: [GroupKind; 3] = [
GroupKind::FailedProjects,
GroupKind::Unconfigured,
GroupKind::Conflicts,
];
pub const UNDER_A_PROJECT: [GroupKind; 3] = [
GroupKind::OutOfTheWay,
GroupKind::HiddenTrees,
GroupKind::Unattributed,
];
pub(crate) fn live(self) -> bool {
match self {
GroupKind::Unconfigured | GroupKind::Conflicts | GroupKind::Unattributed => true,
GroupKind::FailedProjects | GroupKind::HiddenTrees | GroupKind::OutOfTheWay => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Item {
Failed(FailedProject),
Conflict(Conflict),
Loose(LoosePane),
Unconfigured(UnconfiguredPane),
}
pub(crate) fn root_key(tree: &Tree) -> BeadKey {
BeadKey {
project: tree.project.clone(),
id: tree.root.clone(),
}
}
pub(crate) fn marker(open: bool) -> &'static str {
if open {
OPEN
} else {
SHUT
}
}
pub(crate) fn prefix(trunk: &[bool], last: bool, shut: bool, edge: Option<&Edge>) -> String {
let mut drawn = String::from(INDENT);
for more in trunk {
drawn.push_str(if *more { TRUNK } else { GAP });
}
let arm = match edge {
Some(Edge::Blocks) => BLOCKS_ARM,
Some(Edge::ParentChild | Edge::Other(_)) | None => ARM,
};
drawn.push(if last { LAST } else { BRANCH });
drawn.push(arm);
drawn.push(if shut { SHUT_IN_THE_ARM } else { arm });
drawn.push(' ');
drawn
}
pub(crate) fn notes_of(tree: &Tree) -> Vec<Note> {
let mut notes = Vec::new();
if !tree.dangling.is_empty() {
notes.push(Note::Dangling(tree.dangling.len()));
}
if !tree.cycles.is_empty() {
notes.push(Note::Cycle(tree.cycles.len()));
}
notes
}
pub(crate) fn quiet(node: &Node) -> bool {
node.agent.is_none() && node.anomalies.is_empty()
}
pub(crate) fn links_below<'a>(tree: &'a Tree, at: usize, above: &[usize]) -> Vec<&'a Link> {
tree::links_from(&tree.children, at, above)
}
pub(crate) fn beneath(tree: &Tree, at: usize, above: &[usize]) -> Vec<usize> {
#[cfg(test)]
WALKS.with(|walks| walks.set(walks.get() + 1));
tree::beneath(&tree.children, at, above)
}
#[cfg(test)]
thread_local! {
static WALKS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn walks_on_this_thread() -> usize {
WALKS.with(std::cell::Cell::get)
}
pub(crate) fn first_copy(tree: &Tree, at: usize, above: &[usize]) -> bool {
above
.iter()
.copied()
.zip(above.iter().copied().skip(1).chain([at]))
.all(|(from, to)| {
tree.children[from]
.iter()
.any(|link| link.bead == to && link.first)
})
}
pub(crate) fn opens_a_fold(tree: &Tree, at: usize, above: &[usize]) -> bool {
live_beneath(tree, at, above) || ready_beneath(tree, at, above)
}
fn live_beneath(tree: &Tree, at: usize, above: &[usize]) -> bool {
beneath(tree, at, above)
.into_iter()
.any(|node| !quiet(&tree.beads[node]))
}
fn ready_beneath(tree: &Tree, at: usize, above: &[usize]) -> bool {
beneath(tree, at, above)
.into_iter()
.any(|node| tree.beads[node].ready)
}
pub(crate) fn counts_beneath(tree: &Tree, at: usize, above: &[usize]) -> Counts {
Counts::over(
beneath(tree, at, above)
.into_iter()
.map(|node| &tree.beads[node]),
)
}
pub(crate) fn finished(tree: &Tree, at: usize, above: &[usize]) -> bool {
std::iter::once(at)
.chain(beneath(tree, at, above))
.all(|node| tree.beads[node].status.is_closed() && quiet(&tree.beads[node]))
}
pub(crate) fn split<'a>(
tree: &'a Tree,
at: usize,
above: &[usize],
) -> (Vec<&'a Link>, Vec<&'a Link>) {
let below = way_below(above, at);
split_by(tree, at, above, |bead| finished(tree, bead, &below))
}
pub(crate) fn split_by<'a>(
tree: &'a Tree,
at: usize,
above: &[usize],
finished: impl Fn(usize) -> bool,
) -> (Vec<&'a Link>, Vec<&'a Link>) {
let links = links_below(tree, at, above);
let done: Vec<&Link> = links
.iter()
.copied()
.filter(|link| finished(link.bead))
.collect();
if done.len() < MANY {
return (links, Vec::new());
}
let drawn = links
.iter()
.copied()
.filter(|link| !done.contains(link))
.collect();
(drawn, done)
}
pub(crate) fn way_below(above: &[usize], at: usize) -> Vec<usize> {
let mut below = above.to_vec();
below.push(at);
below
}
pub(crate) fn progress_of(tree: &Tree, at: usize, above: &[usize]) -> Option<Progress> {
if links_below(tree, at, above).is_empty() {
return None;
}
let mut counting = vec![at];
counting.extend(beneath(tree, at, above));
Some(Progress {
total: counting.len(),
closed: counting
.into_iter()
.filter(|node| tree.beads[*node].status.is_closed())
.count(),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BeadFacts {
pub progress: Option<Progress>,
pub beneath: Counts,
pub opens_a_fold: bool,
pub finished: bool,
}
pub(crate) fn facts_of(tree: &Tree, at: usize, above: &[usize]) -> BeadFacts {
BeadFacts {
progress: progress_of(tree, at, above),
beneath: counts_beneath(tree, at, above),
opens_a_fold: opens_a_fold(tree, at, above),
finished: finished(tree, at, above),
}
}
pub(crate) fn run_size(tree: &Tree, members: &[&Link], above: &[usize]) -> usize {
let mut seen: BTreeSet<usize> = members.iter().map(|link| link.bead).collect();
for member in members {
seen.extend(beneath(tree, member.bead, above));
}
seen.len()
}