use std::time::{Duration, Instant};
use crate::editor::EditorState;
pub const YANK_FLASH_DURATION: Duration = Duration::from_millis(150);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct YankFlash {
pub start: usize,
pub end: usize,
pub armed_at: Instant,
pub armed_version: u64,
}
impl EditorState {
pub fn flash_yank(&mut self, start_char: usize, end_char: usize) {
if start_char >= end_char {
self.yank_flash = None;
return;
}
let rope = self.buffer.rope();
let len = rope.len_chars();
let start = rope.char_to_byte(start_char.min(len));
let end = rope.char_to_byte(end_char.min(len));
if start >= end {
self.yank_flash = None;
return;
}
self.yank_flash = Some(YankFlash {
start,
end,
armed_at: Instant::now(),
armed_version: self.buffer.version(),
});
}
pub fn active_yank_flash(&self) -> Option<YankFlash> {
let version = self.buffer.version();
self.yank_flash
.filter(|f| f.armed_version == version && f.armed_at.elapsed() < YANK_FLASH_DURATION)
}
pub fn yank_flash_deadline(&self) -> Option<Instant> {
self.yank_flash.map(|f| f.armed_at + YANK_FLASH_DURATION)
}
pub fn expire_yank_flash(&mut self) -> bool {
if self.yank_flash.is_some() && self.active_yank_flash().is_none() {
self.yank_flash = None;
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Theme;
use crate::document::Buffer;
use crate::editor::EditorState;
fn editor(text: &str) -> EditorState {
let theme: &'static Theme = Box::leak(Box::new(Theme::default()));
EditorState::new(Buffer::from_str(text), theme)
}
#[test]
fn flash_yank_records_byte_range() {
let mut ed = editor("héllo");
ed.flash_yank(0, 3);
let f = ed.yank_flash.expect("flash armed");
assert_eq!((f.start, f.end), (0, 4)); assert!(ed.active_yank_flash().is_some());
}
#[test]
fn empty_span_arms_nothing() {
let mut ed = editor("hello");
ed.flash_yank(2, 2);
assert!(ed.yank_flash.is_none());
}
#[test]
fn out_of_range_chars_are_clamped() {
let mut ed = editor("hi");
ed.flash_yank(0, 999);
let f = ed.yank_flash.expect("flash armed");
assert_eq!((f.start, f.end), (0, 2));
}
#[test]
fn buffer_mutation_invalidates_the_flash() {
let mut ed = editor("hello");
ed.flash_yank(0, 3);
assert!(ed.active_yank_flash().is_some());
ed.buffer.insert(0, "x");
assert!(ed.active_yank_flash().is_none());
assert!(ed.expire_yank_flash());
assert!(ed.yank_flash.is_none());
}
#[test]
fn expired_flash_is_inactive_and_clears() {
let mut ed = editor("hello");
ed.flash_yank(0, 3);
if let Some(f) = ed.yank_flash.as_mut() {
f.armed_at = Instant::now() - YANK_FLASH_DURATION - Duration::from_millis(5);
}
assert!(ed.active_yank_flash().is_none());
assert!(ed.yank_flash_deadline().is_some());
assert!(ed.expire_yank_flash());
assert!(ed.yank_flash.is_none());
assert!(!ed.expire_yank_flash());
}
}