use std::collections::{BTreeMap, HashMap};
use std::convert::Infallible;
use std::fmt;
use std::hash::Hash;
use std::sync::{Mutex, PoisonError};
use rich::{Console, ConsoleOptions, LineRenderable, Overflow, Renderable, Segment, Table, Text};
use super::data::{normalize, TableData};
use super::sort::{compare_rows, SortKey};
use super::{frame_builders, headers, style, Column, Frame, Value};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Window {
#[default]
All,
Head(usize),
Tail(usize),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct RenderStats {
pub frames: u64,
pub rows_prepared: u64,
pub rows_rendered: u64,
pub relayouts: u64,
}
struct Entry<K> {
key: K,
values: Vec<Value>,
version: u64,
}
struct RowCache {
version: u64,
cells: Vec<Text>,
widths: Vec<usize>,
lines: Option<Vec<Vec<Segment>>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct LayoutKey {
width: usize,
maxima: Vec<usize>,
ascii_only: bool,
safe_box: bool,
legacy_windows: bool,
}
#[derive(Default)]
struct Cache {
rows: HashMap<u64, RowCache>,
layout: Option<LayoutKey>,
stats: RenderStats,
}
pub struct StreamingTable<K> {
columns: Vec<Column>,
frame: Frame,
window: Window,
capacity: Option<usize>,
sort: Vec<SortKey>,
entries: BTreeMap<u64, Entry<K>>,
index: HashMap<K, u64>,
next_seq: u64,
next_version: u64,
evicted: u64,
cache: Mutex<Cache>,
}
impl<K> fmt::Debug for StreamingTable<K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StreamingTable")
.field("columns", &self.columns)
.field("rows", &self.entries.len())
.field("window", &self.window)
.field("capacity", &self.capacity)
.field("sort", &self.sort)
.field("evicted", &self.evicted)
.finish()
}
}
frame_builders!([K] StreamingTable<K>);
impl<K: Eq + Hash + Clone> StreamingTable<K> {
pub fn new(columns: impl IntoIterator<Item = Column>) -> Self {
StreamingTable {
columns: columns.into_iter().collect(),
frame: Frame::default(),
window: Window::All,
capacity: None,
sort: Vec::new(),
entries: BTreeMap::new(),
index: HashMap::new(),
next_seq: 0,
next_version: 0,
evicted: 0,
cache: Mutex::new(Cache::default()),
}
}
pub fn window(mut self, window: Window) -> Self {
self.window = window;
self
}
pub fn set_window(&mut self, window: Window) {
self.window = window;
}
pub fn capacity(mut self, rows: usize) -> Self {
self.capacity = Some(rows);
self.evict();
self
}
pub fn set_sort(&mut self, keys: impl IntoIterator<Item = SortKey>) {
self.sort = keys.into_iter().collect();
}
pub fn sort_by(mut self, keys: impl IntoIterator<Item = SortKey>) -> Self {
self.set_sort(keys);
self
}
fn version(&mut self) -> u64 {
self.next_version += 1;
self.next_version
}
fn evict(&mut self) {
let Some(capacity) = self.capacity else {
return;
};
while self.entries.len() > capacity {
let Some((_, entry)) = self.entries.pop_first() else {
break;
};
self.index.remove(&entry.key);
self.evicted += 1;
}
}
pub fn upsert(&mut self, key: K, row: impl IntoIterator<Item = Value>) -> bool {
let values = normalize(row, self.columns.len());
if let Some(&seq) = self.index.get(&key) {
let entry = self.entries.get(&seq).expect("indexed rows exist");
if entry.values != values {
let version = self.version();
let entry = self.entries.get_mut(&seq).expect("indexed rows exist");
entry.values = values;
entry.version = version;
}
return false;
}
let seq = self.next_seq;
self.next_seq += 1;
let version = self.version();
self.index.insert(key.clone(), seq);
self.entries.insert(
seq,
Entry {
key,
values,
version,
},
);
self.evict();
true
}
pub fn update_cell(&mut self, key: &K, column: usize, value: impl Into<Value>) -> bool {
let value = value.into();
let Some(&seq) = self.index.get(key) else {
return false;
};
if column >= self.columns.len() {
return false;
}
if self.entries[&seq].values[column] != value {
let version = self.version();
let entry = self.entries.get_mut(&seq).expect("indexed rows exist");
entry.values[column] = value;
entry.version = version;
}
true
}
pub fn remove(&mut self, key: &K) -> Option<Vec<Value>> {
let seq = self.index.remove(key)?;
self.entries.remove(&seq).map(|entry| entry.values)
}
pub fn clear(&mut self) {
self.entries.clear();
self.index.clear();
}
pub fn get(&self, key: &K) -> Option<&[Value]> {
let seq = self.index.get(key)?;
self.entries.get(seq).map(|entry| entry.values.as_slice())
}
pub fn contains_key(&self, key: &K) -> bool {
self.index.contains_key(key)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn evicted(&self) -> u64 {
self.evicted
}
pub fn columns(&self) -> &[Column] {
&self.columns
}
pub fn rows(&self) -> impl Iterator<Item = (&K, &[Value])> + '_ {
self.entries
.values()
.map(|entry| (&entry.key, entry.values.as_slice()))
}
pub fn stats(&self) -> RenderStats {
self.lock().stats
}
pub fn reset_stats(&self) {
self.lock().stats = RenderStats::default();
}
pub fn invalidate(&self) {
let mut cache = self.lock();
cache.rows.clear();
cache.layout = None;
}
fn lock(&self) -> std::sync::MutexGuard<'_, Cache> {
self.cache.lock().unwrap_or_else(PoisonError::into_inner)
}
fn order(&self) -> Vec<u64> {
let mut order: Vec<u64> = self.entries.keys().copied().collect();
if !self.sort.is_empty() {
order.sort_by(|a, b| {
compare_rows(&self.entries[a].values, &self.entries[b].values, &self.sort)
});
}
order
}
fn visible(&self) -> (Vec<u64>, u64, u64) {
let order = self.order();
let total = order.len();
match self.window {
Window::All => (order, self.evicted, 0),
Window::Head(n) => {
let shown = n.min(total);
(
order[..shown].to_vec(),
self.evicted,
(total - shown) as u64,
)
}
Window::Tail(n) => {
let start = total.saturating_sub(n);
(order[start..].to_vec(), self.evicted + start as u64, 0)
}
}
}
pub fn to_data(&self) -> TableData {
let mut data = TableData::new(self.columns.clone());
data.frame = self.frame.clone();
for seq in self.order() {
data.push(self.entries[&seq].values.iter().cloned());
}
data.sort_by(self.sort.iter().copied())
}
pub fn to_table(&self, console: &Console) -> Table {
let headers = headers(console, &self.columns, &self.sort);
let mut table = self.frame.table(&self.columns, &headers, true, true);
for seq in self.visible().0 {
let values = &self.entries[&seq].values;
table.add_row_text(
self.columns
.iter()
.zip(values)
.map(|(column, value)| column.cell(value))
.collect(),
);
}
table
}
fn indicator(
console: &Console,
options: &ConsoleOptions,
count: u64,
what: &str,
) -> Vec<Vec<Segment>> {
let ellipsis = if console.ascii_only() { "..." } else { "…" };
let plural = if count == 1 { "" } else { "s" };
let text = Text::styled(
format!("{ellipsis} {count} {what} row{plural}"),
style(console, "table.more"),
)
.no_wrap(true)
.overflow(Overflow::Ellipsis);
let mut options = options.clone();
options.height = None;
console.render_lines(&text, &options, false)
}
fn render_lines(&self, console: &Console, options: &ConsoleOptions) -> Vec<Vec<Segment>> {
let mut cache = self.lock();
let cache = &mut *cache;
cache.stats.frames += 1;
let (visible, earlier, later) = self.visible();
let headers = headers(console, &self.columns, &self.sort);
let mut out = Vec::new();
if earlier > 0 {
out.extend(Self::indicator(console, options, earlier, "earlier"));
}
let shown: std::collections::HashSet<u64> = visible.iter().copied().collect();
cache.rows.retain(|seq, _| shown.contains(seq));
if self.columns.is_empty() || visible.is_empty() {
let table = self.frame.table(&self.columns, &headers, true, true);
out.extend(table_lines(&table, console, options));
} else {
self.render_rows(console, options, cache, &visible, &headers, &mut out);
}
if later > 0 {
out.extend(Self::indicator(console, options, later, "more"));
}
out
}
fn render_rows(
&self,
console: &Console,
options: &ConsoleOptions,
cache: &mut Cache,
visible: &[u64],
headers: &[Text],
out: &mut Vec<Vec<Segment>>,
) {
for seq in visible {
let entry = &self.entries[seq];
if cache
.rows
.get(seq)
.is_some_and(|row| row.version == entry.version)
{
continue;
}
let cells: Vec<Text> = self
.columns
.iter()
.zip(&entry.values)
.map(|(column, value)| column.cell(value))
.collect();
let widths = cells.iter().map(|cell| cell.measurement().1).collect();
cache.rows.insert(
*seq,
RowCache {
version: entry.version,
cells,
widths,
lines: None,
},
);
cache.stats.rows_prepared += 1;
}
let mut widest: Vec<(usize, Option<u64>)> = headers
.iter()
.map(|header| (header.measurement().1, None))
.collect();
for seq in visible {
for (best, &width) in widest.iter_mut().zip(&cache.rows[seq].widths) {
if width > best.0 {
*best = (width, Some(*seq));
}
}
}
let proxy: Vec<Text> = widest
.iter()
.enumerate()
.map(|(column, (_, seq))| match seq {
Some(seq) => cache.rows[seq].cells[column].clone(),
None => headers[column].clone(),
})
.collect();
let key = LayoutKey {
width: options.max_width,
maxima: widest.iter().map(|(width, _)| *width).collect(),
ascii_only: console.ascii_only(),
safe_box: console.safe_box(),
legacy_windows: console.legacy_windows(),
};
if cache.layout.as_ref() != Some(&key) {
for row in cache.rows.values_mut() {
row.lines = None;
}
cache.layout = Some(key);
cache.stats.relayouts += 1;
}
let edge = self.frame.edge_lines();
let render = |frame: &Frame, rows: &[&[Text]], show_header: bool| {
let mut table = frame.table(&self.columns, headers, show_header, true);
for row in rows {
table.add_row_text(row.to_vec());
}
table_lines(&table, console, options)
};
let bare = Frame {
title: None,
caption: None,
..self.frame.clone()
};
let proxy_height = render(&bare, &[&proxy], false).len() - 2 * edge;
let head = Frame {
caption: None,
..self.frame.clone()
};
let head = render(&head, &[&proxy], true);
out.extend_from_slice(&head[..head.len() - proxy_height - edge]);
for seq in visible {
let row = cache.rows.get_mut(seq).expect("prepared above");
if row.lines.is_none() {
let lines = render(&bare, &[&proxy, &row.cells], false);
row.lines = Some(lines[edge + proxy_height..lines.len() - edge].to_vec());
cache.stats.rows_rendered += 1;
}
out.extend(row.lines.iter().flatten().cloned());
}
let foot = Frame {
title: None,
..self.frame.clone()
};
let foot = render(&foot, &[&proxy], false);
out.extend_from_slice(&foot[edge + proxy_height..]);
}
}
fn table_lines(table: &Table, console: &Console, options: &ConsoleOptions) -> Vec<Vec<Segment>> {
let mut lines = Vec::new();
let result: Result<(), Infallible> = table.try_for_each_line(console, options, |line| {
lines.push(line);
Ok(())
});
match result {
Ok(()) => lines,
Err(never) => match never {},
}
}
impl<K: Eq + Hash + Clone> Renderable for StreamingTable<K> {
fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
crate::event::flatten(self.render_lines(console, options))
}
fn measure(&self, console: &Console, options: &ConsoleOptions) -> rich::measure::Measurement {
self.to_table(console).measure(console, options)
}
}
impl<K: Eq + Hash + Clone> crate::a11y::AccessibleText for StreamingTable<K> {
fn accessible_text(&self, width: usize) -> String {
let console = Console::builder().width(width.max(1)).build();
self.to_table(&console).accessible_text(width)
}
}