use std::sync::mpsc::{self, Receiver, Sender};
use std::time::{Duration, Instant};
use crate::tui::highlight::{DiffContext, RenderedDiff, prepare_diff};
const SLOW: Duration = Duration::from_millis(120);
pub struct DiffFeed {
tx: Sender<(u64, RenderedDiff)>,
rx: Receiver<(u64, RenderedDiff)>,
seq: u64,
loading: bool,
since: Option<Instant>,
}
impl Default for DiffFeed {
fn default() -> Self {
let (tx, rx) = mpsc::channel();
DiffFeed {
tx,
rx,
seq: 0,
loading: false,
since: None,
}
}
}
impl DiffFeed {
pub fn request(&mut self, raw: impl FnOnce() -> (String, DiffContext) + Send + 'static) {
self.seq += 1;
self.loading = true;
self.since = Some(Instant::now());
let (seq, tx) = (self.seq, self.tx.clone());
std::thread::spawn(move || {
let (text, ctx) = raw();
let _ = tx.send((seq, prepare_diff(&text, ctx)));
});
}
pub fn idle(&mut self) {
self.seq += 1;
self.loading = false;
self.since = None;
}
pub fn take(&mut self) -> Option<RenderedDiff> {
let mut newest = None;
while let Ok((seq, prepared)) = self.rx.try_recv() {
if seq == self.seq {
newest = Some(prepared);
self.loading = false;
}
}
newest
}
pub fn loading(&self) -> bool {
self.loading
}
pub fn slow(&self) -> bool {
self.loading && self.since.is_some_and(|t| t.elapsed() >= SLOW)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty() -> RenderedDiff {
RenderedDiff::default()
}
fn nothing() -> (String, DiffContext) {
(String::new(), DiffContext::default())
}
#[test]
fn an_answer_to_a_row_you_have_left_is_dropped() {
let mut feed = DiffFeed::default();
let (first, second) = (feed.tx.clone(), feed.tx.clone());
feed.request(nothing); feed.request(nothing);
let _ = first.send((1, empty()));
assert!(feed.take().is_none(), "the answer to seq 1 is not wanted");
let _ = second.send((2, empty()));
assert!(feed.take().is_some(), "the answer to seq 2 is");
assert!(!feed.loading(), "and the pane has stopped waiting");
}
#[test]
fn a_row_with_no_diff_stops_the_waiting() {
let mut feed = DiffFeed::default();
feed.request(nothing);
assert!(feed.loading());
feed.idle();
assert!(
!feed.loading(),
"nothing is coming, so nothing is waited for"
);
assert!(!feed.slow());
}
}