use std::collections::BTreeSet;
use accounting::lineage::RunLine;
use kernel::RunId;
use super::arrange::{Entry, NodeKey, arrange};
use super::detail::{json_lines, line_lines};
use super::follow::Row;
use super::keys::Action;
use super::list::{beside, cut, has_children, record_lines, scrolled, tree_lines, visible};
use super::rounds::{Rounds, fold};
pub(super) const SIDE_PANE_MIN_WIDTH: usize = 110;
pub(super) const FILLING: &str = "filling in the older ledger lines";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Size {
pub(super) columns: usize,
pub(super) rows: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Lens {
Tree,
Records,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Detail {
Pane,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Step {
Back(usize),
On(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Reach {
Window,
Whole,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Life {
Open,
Closed,
}
pub(super) struct Face {
runs: Vec<RunLine>,
rounds: Rounds,
entries: Vec<Entry>,
records: Vec<Row>,
lens: Lens,
tree_at: usize,
record_at: usize,
expanded: BTreeSet<usize>,
detail: Detail,
life: Life,
reach: Reach,
size: Size,
}
impl Face {
pub(super) fn open(runs: &[RunLine], records: Vec<Row>, size: Size) -> Face {
let entries = arrange(runs, &Rounds::new());
let latest = |keep: fn(&RunLine) -> bool| {
runs.iter()
.filter(|line| keep(line))
.max_by_key(|line| line.first_seq)
.map(|line| line.run)
};
let chosen = latest(|line| line.unanswered > 0)
.or_else(|| latest(|line| line.state == Some(storage::RunPhase::Active)))
.or_else(|| latest(|_| true));
let tree_at = chosen
.and_then(|run| {
entries
.iter()
.position(|entry| entry.key == NodeKey::Run(run))
})
.unwrap_or(0);
let mut face = Face {
runs: runs.to_vec(),
rounds: Rounds::new(),
entries,
records,
lens: Lens::Tree,
tree_at: 0,
record_at: 0,
expanded: BTreeSet::new(),
detail: Detail::Pane,
life: Life::Open,
reach: Reach::Whole,
size,
};
face.select_entry(tree_at);
face
}
pub(super) fn open_window(runs: &[RunLine], window: Vec<Row>, size: Size) -> Face {
Face {
reach: Reach::Window,
..Face::open(runs, window, size)
}
}
pub(super) fn fill(&mut self, runs: &[RunLine], whole: Vec<Row>) {
let line_seq = self.records.get(self.record_at).map(|row| row.seq);
self.records = whole;
self.record_at = self.first_record_from(line_seq);
let opened: Vec<RunId> = self.rounds.keys().copied().collect();
for run in opened {
self.rounds.insert(run, fold(run, &self.records));
}
self.runs = runs.to_vec();
self.reach = Reach::Whole;
self.rearrange();
self.open_ancestors(self.tree_at);
}
pub(super) fn apply(&mut self, action: Action) {
let page = self.size.rows.max(1);
match action {
Action::Up => self.step(Step::Back(1)),
Action::Down => self.step(Step::On(1)),
Action::PageUp => self.step(Step::Back(page)),
Action::PageDown => self.step(Step::On(page)),
Action::First => self.step(Step::Back(usize::MAX)),
Action::Last => self.step(Step::On(usize::MAX)),
Action::Collapse => self.collapse(),
Action::Expand => self.expand(),
Action::SwitchLens => self.switch_lens(),
Action::OpenDetail => self.detail = Detail::Full,
Action::CloseDetail => self.detail = Detail::Pane,
Action::Quit => self.life = Life::Closed,
}
}
pub(super) fn resize(&mut self, size: Size) {
self.size = size;
}
pub(super) fn follow(&mut self, runs: &[RunLine], appended: Vec<Row>) {
let stale: BTreeSet<RunId> = appended
.iter()
.map(|row| row.run)
.filter(|run| self.rounds.contains_key(run))
.collect();
self.records.extend(appended);
for run in stale {
self.rounds.insert(run, fold(run, &self.records));
}
self.runs = runs.to_vec();
self.rearrange();
}
fn rearrange(&mut self) {
let selected = self
.entries
.get(self.tree_at)
.map(|entry| entry.key.clone());
let open: BTreeSet<NodeKey> = self
.expanded
.iter()
.filter_map(|at| self.entries.get(*at).map(|entry| entry.key.clone()))
.collect();
let known: BTreeSet<NodeKey> = self
.entries
.iter()
.filter(|entry| matches!(entry.key, NodeKey::Run(_)))
.map(|entry| entry.key.clone())
.collect();
self.entries = arrange(&self.runs, &self.rounds);
self.expanded = self
.entries
.iter()
.enumerate()
.filter(|(_, entry)| open.contains(&entry.key))
.map(|(at, _)| at)
.collect();
let fresh: Vec<usize> = self
.entries
.iter()
.enumerate()
.filter(|(_, entry)| {
matches!(entry.key, NodeKey::Run(_)) && !known.contains(&entry.key)
})
.map(|(at, _)| at)
.collect();
for at in fresh {
self.open_ancestors(at);
}
self.tree_at = selected
.and_then(|key| self.entries.iter().position(|entry| entry.key == key))
.unwrap_or(0);
}
pub(super) fn is_closed(&self) -> bool {
self.life == Life::Closed
}
pub(super) fn frame(&self) -> Vec<String> {
match self.reach {
Reach::Whole => self.body(self.size.rows),
Reach::Window => {
let rows = self.size.rows.saturating_sub(1);
let mut lines = self.body(rows);
lines.resize(rows, String::new());
lines.push(cut(FILLING, self.size.columns));
lines
}
}
}
fn body(&self, rows: usize) -> Vec<String> {
let columns = self.size.columns;
let detail = self.detail_lines();
if self.detail == Detail::Full {
return detail
.iter()
.take(rows)
.map(|line| cut(line, columns))
.collect();
}
let list = self.list_lines(rows);
if columns < SIDE_PANE_MIN_WIDTH {
return list.iter().map(|line| cut(line, columns)).collect();
}
beside(&list, &detail, columns, rows)
}
fn step(&mut self, step: Step) {
let move_by = |at: usize, len: usize| match step {
Step::Back(by) => at.saturating_sub(by),
Step::On(by) => at.saturating_add(by).min(len.saturating_sub(1)),
};
match self.lens {
Lens::Tree => {
let shown = visible(&self.entries, &self.expanded);
let at = shown.iter().position(|at| *at == self.tree_at).unwrap_or(0);
if let Some(entry) = shown.get(move_by(at, shown.len())) {
self.tree_at = *entry;
}
}
Lens::Records => self.record_at = move_by(self.record_at, self.records.len()),
}
}
fn collapse(&mut self) {
if self.lens == Lens::Records {
return;
}
if !self.expanded.remove(&self.tree_at)
&& let Some(parent) = self
.entries
.get(self.tree_at)
.and_then(|entry| entry.parent)
{
self.tree_at = parent;
}
}
fn expand(&mut self) {
if self.lens == Lens::Records {
return;
}
if let Some(&NodeKey::Run(run)) = self.entries.get(self.tree_at).map(|entry| &entry.key)
&& !self.rounds.contains_key(&run)
{
self.rounds.insert(run, fold(run, &self.records));
self.rearrange();
}
if !has_children(&self.entries, self.tree_at) {
return;
}
if !self.expanded.insert(self.tree_at) {
self.tree_at = self.tree_at.saturating_add(1);
}
}
fn switch_lens(&mut self) {
match self.lens {
Lens::Tree => {
self.record_at =
self.first_record_from(self.entries.get(self.tree_at).map(|entry| entry.seq));
self.lens = Lens::Records;
}
Lens::Records => {
let key = self.records.get(self.record_at).map(|row| {
if row.run == RunId::CITY {
NodeKey::City
} else {
NodeKey::Run(row.run)
}
});
if let Some(at) =
key.and_then(|key| self.entries.iter().position(|entry| entry.key == key))
{
self.select_entry(at);
}
self.lens = Lens::Tree;
}
}
}
fn first_record_from(&self, seq: Option<kernel::Seq>) -> usize {
seq.map_or(0, |seq| {
self.records
.partition_point(|row| row.seq < seq)
.min(self.records.len().saturating_sub(1))
})
}
fn select_entry(&mut self, at: usize) {
self.tree_at = at;
self.open_ancestors(at);
}
fn open_ancestors(&mut self, at: usize) {
let mut parent = self.entries.get(at).and_then(|entry| entry.parent);
while let Some(up) = parent {
self.expanded.insert(up);
parent = self.entries.get(up).and_then(|entry| entry.parent);
}
}
fn list_lines(&self, rows: usize) -> Vec<String> {
match self.lens {
Lens::Tree => {
let shown = visible(&self.entries, &self.expanded);
let cursor = shown.iter().position(|at| *at == self.tree_at).unwrap_or(0);
let lines = tree_lines(&self.entries, &shown, &self.expanded, &self.rounds);
scrolled(lines, cursor, rows)
}
Lens::Records => record_lines(&self.records, self.record_at, rows),
}
}
fn detail_lines(&self) -> Vec<String> {
match self.lens {
Lens::Tree => self
.entries
.get(self.tree_at)
.map(|entry| json_lines(&entry.detail))
.unwrap_or_default(),
Lens::Records => self
.records
.get(self.record_at)
.map(|row| line_lines(&row.line))
.unwrap_or_default(),
}
}
}