fix: refresh cached diff colors for external theme updates
This commit is contained in:
@@ -7,6 +7,7 @@ use std::{
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyEventState, KeyModifiers};
|
||||
|
||||
const QUERY_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
const REFRESH_INTERVAL: Duration = Duration::from_secs(1);
|
||||
const ESCAPE_TIMEOUT: Duration = Duration::from_millis(25);
|
||||
const HEADERS: [&[u8]; 3] = [b"11;", b"4;1;", b"4;2;"];
|
||||
const MAX_BODY: usize = 32;
|
||||
@@ -39,6 +40,7 @@ enum Capture {
|
||||
pub(crate) struct TerminalColors {
|
||||
pub colors: Option<ThemeColors>,
|
||||
query: Option<Query>,
|
||||
refresh_at: Option<Instant>,
|
||||
capture: Capture,
|
||||
original: Vec<Event>,
|
||||
body: [u8; MAX_BODY],
|
||||
@@ -47,8 +49,13 @@ pub(crate) struct TerminalColors {
|
||||
}
|
||||
|
||||
impl TerminalColors {
|
||||
pub fn refresh_due(&self, now: Instant) -> bool {
|
||||
self.query.is_none() && self.refresh_at.is_none_or(|deadline| now >= deadline)
|
||||
}
|
||||
|
||||
pub fn query(&mut self, output: &mut impl Write, now: Instant) -> io::Result<()> {
|
||||
self.expire(now);
|
||||
self.refresh_at = Some(now + REFRESH_INTERVAL);
|
||||
self.query = None;
|
||||
if let Err(error) = output
|
||||
.write_all(b"\x1b]11;?\x1b\\\x1b]4;1;?;2;?\x1b\\")
|
||||
@@ -397,6 +404,55 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn periodic_refresh_detects_silent_change_without_overlapping_queries() {
|
||||
let now = Instant::now();
|
||||
let mut collector = TerminalColors::default();
|
||||
let mut output = Vec::new();
|
||||
assert!(collector.refresh_due(now));
|
||||
collector.query(&mut output, now).unwrap();
|
||||
report(&mut collector, "11;#101010", now);
|
||||
report(&mut collector, "4;1;#FF0000", now);
|
||||
report(&mut collector, "4;2;#00FF00", now);
|
||||
let previous = collector.colors;
|
||||
assert!(previous.is_some());
|
||||
|
||||
// The terminal changes its palette without emitting a notification.
|
||||
let refresh = now + Duration::from_secs(1);
|
||||
assert!(!collector.refresh_due(refresh - Duration::from_nanos(1)));
|
||||
assert!(collector.refresh_due(refresh));
|
||||
collector.query(&mut output, refresh).unwrap();
|
||||
report(&mut collector, "11;#EEEEEE", refresh);
|
||||
report(&mut collector, "4;1;#AA1122", refresh);
|
||||
assert_eq!(collector.colors, previous);
|
||||
let typed = key(KeyCode::Char('i'), KeyModifiers::NONE);
|
||||
collector.push(typed.clone(), refresh);
|
||||
assert_eq!(drain(&mut collector), vec![typed]);
|
||||
|
||||
// Even an overdue probe must wait until the incomplete query expires.
|
||||
let retry = refresh + Duration::from_secs(1);
|
||||
assert!(!collector.refresh_due(retry));
|
||||
collector.expire(retry);
|
||||
assert_eq!(collector.colors, None);
|
||||
assert!(collector.refresh_due(retry));
|
||||
collector.query(&mut output, retry).unwrap();
|
||||
report(&mut collector, "11;#EEEEEE", retry);
|
||||
report(&mut collector, "4;1;#AA1122", retry);
|
||||
assert_eq!(collector.colors, None);
|
||||
report(&mut collector, "4;2;#22BB33", retry);
|
||||
assert_eq!(
|
||||
collector.colors,
|
||||
Some(ThemeColors {
|
||||
background: [238, 238, 238],
|
||||
red: [170, 17, 34],
|
||||
green: [34, 187, 51],
|
||||
})
|
||||
);
|
||||
assert!(drain(&mut collector).is_empty());
|
||||
assert!(!collector.refresh_due(retry + Duration::from_millis(999)));
|
||||
assert!(collector.refresh_due(retry + Duration::from_secs(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_refresh_clears_stale_colors_and_consumes_late_partial_reply() {
|
||||
let now = Instant::now();
|
||||
|
||||
Reference in New Issue
Block a user