use std::time::{Duration, Instant};
#[cfg(not(windows))]
const BURST_INTERVAL: Duration = Duration::from_millis(8);
#[cfg(windows)]
const BURST_INTERVAL: Duration = Duration::from_millis(30);
const MIN_BURST_LEN: usize = 4;
pub struct PasteBurstDetector {
last_key_time: Option<Instant>,
burst_len: usize,
lines_before_burst: usize,
}
impl PasteBurstDetector {
pub fn new() -> Self {
Self { last_key_time: None, burst_len: 0, lines_before_burst: 1 }
}
pub fn on_key_event(&mut self, current_line_count: usize) -> bool {
let now = Instant::now();
if let Some(last) = self.last_key_time {
if now.duration_since(last) <= BURST_INTERVAL {
self.burst_len += 1;
} else {
self.burst_len = 1;
self.lines_before_burst = current_line_count;
}
} else {
self.burst_len = 1;
self.lines_before_burst = current_line_count;
}
self.last_key_time = Some(now);
self.is_paste()
}
#[must_use]
pub fn is_paste(&self) -> bool {
self.burst_len >= MIN_BURST_LEN
}
#[must_use]
pub fn is_active(&self) -> bool {
self.last_key_time.is_some_and(|last| last.elapsed() <= BURST_INTERVAL)
}
#[must_use]
pub fn is_settled(&self) -> bool {
self.is_paste() && !self.is_active()
}
#[must_use]
pub fn lines_added(&self, current_line_count: usize) -> usize {
current_line_count.saturating_sub(self.lines_before_burst)
}
pub fn reset(&mut self) {
self.last_key_time = None;
self.burst_len = 0;
}
}
impl Default for PasteBurstDetector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_burst_on_single_key() {
let mut d = PasteBurstDetector::new();
assert!(!d.on_key_event(1));
}
#[test]
fn no_burst_on_slow_typing() {
let mut d = PasteBurstDetector::new();
d.on_key_event(1);
d.last_key_time = Instant::now().checked_sub(Duration::from_millis(200));
assert!(!d.on_key_event(1));
}
#[test]
fn burst_after_min_rapid_keys() {
let mut d = PasteBurstDetector::new();
for _ in 0..MIN_BURST_LEN {
d.on_key_event(1);
}
assert!(d.is_paste());
}
#[test]
fn reset_clears_burst() {
let mut d = PasteBurstDetector::new();
for _ in 0..MIN_BURST_LEN {
d.on_key_event(1);
}
assert!(d.is_paste());
d.reset();
assert!(!d.is_paste());
}
#[test]
fn lines_added_tracks_growth() {
let mut d = PasteBurstDetector::new();
d.on_key_event(1); d.on_key_event(3); assert_eq!(d.lines_added(5), 4); }
#[test]
fn active_while_recent_key() {
let mut d = PasteBurstDetector::new();
d.on_key_event(1);
assert!(d.is_active());
}
#[test]
fn settled_after_idle_gap() {
let mut d = PasteBurstDetector::new();
for _ in 0..MIN_BURST_LEN {
d.on_key_event(1);
}
let idle_gap = BURST_INTERVAL + Duration::from_millis(1);
d.last_key_time = Instant::now().checked_sub(idle_gap);
assert!(d.is_settled());
}
}