use super::is_listed;
use crate::config::{CacheExpiryConfig, IconSet};
use crate::icons::cache_expiry_icon;
use crate::input::ContextWindow;
use crate::segments::tokens::format_tokens;
use crate::theme::Palette;
pub const NAME: &str = "cache_expiry";
pub fn is_visible(segments: &[String], config: &CacheExpiryConfig) -> bool {
config.enabled && is_listed(segments, NAME)
}
pub fn render_cache_expiry_segment(
ctx: &ContextWindow,
idle_seconds: Option<u64>,
config: &CacheExpiryConfig,
icon_set: IconSet,
palette: &Palette,
) -> Option<String> {
let idle = idle_seconds?;
if !config.enabled || idle < config.idle_seconds {
return None;
}
let context_tokens = ctx.current_context_tokens.unwrap_or(0);
if context_tokens < config.min_tokens {
return None;
}
let icon = cache_expiry_icon(icon_set, config.prefix.as_deref());
let r = palette.reset;
let context = if config.show_tokens {
format!(" · {} context", format_tokens(Some(context_tokens)))
} else {
String::new()
};
Some(format!(
"{}{}{} {}{}{}{} · cache likely cold{}{}",
palette.label,
icon,
r,
palette.tokens_alert,
format_idle(idle),
r,
palette.dim,
context,
r
))
}
fn format_idle(seconds: u64) -> String {
let (hours, minutes) = (seconds / 3_600, seconds % 3_600 / 60);
match (hours, minutes) {
(0, 0) => format!("{seconds}s"),
(0, m) => format!("{m}m"),
(h, 0) => format!("{h}h"),
(h, m) => format!("{h}h {m}m"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::segments::names;
fn ctx_with(tokens: u64) -> ContextWindow {
ContextWindow {
current_context_tokens: Some(tokens),
..Default::default()
}
}
fn render(ctx: &ContextWindow, idle: Option<u64>, cfg: &CacheExpiryConfig) -> Option<String> {
render_cache_expiry_segment(ctx, idle, cfg, IconSet::Plain, &Palette::for_theme("plain"))
}
#[test]
fn test_reminds_after_thirty_idle_minutes() {
let cfg = CacheExpiryConfig::default();
assert_eq!(
render(&ctx_with(92_000), Some(1_800), &cfg).as_deref(),
Some("Idle: 30m · cache likely cold")
);
}
#[test]
fn test_quiet_through_ordinary_pauses() {
let cfg = CacheExpiryConfig::default();
assert_eq!(render(&ctx_with(92_000), Some(300), &cfg), None);
assert_eq!(render(&ctx_with(92_000), Some(1_799), &cfg), None);
}
#[test]
fn test_quiet_when_idle_time_is_unknown() {
let cfg = CacheExpiryConfig::default();
assert_eq!(render(&ctx_with(92_000), None, &cfg), None);
}
#[test]
fn test_quiet_below_the_token_floor() {
let cfg = CacheExpiryConfig::default();
assert_eq!(render(&ctx_with(49_999), Some(3_600), &cfg), None);
assert!(render(&ctx_with(50_000), Some(3_600), &cfg).is_some());
}
#[test]
fn test_quiet_when_disabled() {
let cfg = CacheExpiryConfig {
enabled: false,
..Default::default()
};
assert_eq!(render(&ctx_with(92_000), Some(3_600), &cfg), None);
}
#[test]
fn test_custom_threshold_and_floor() {
let cfg = CacheExpiryConfig {
idle_seconds: 3_600,
min_tokens: 10_000,
..Default::default()
};
assert_eq!(render(&ctx_with(20_000), Some(600), &cfg), None);
assert!(render(&ctx_with(20_000), Some(3_600), &cfg).is_some());
}
#[test]
fn test_idle_time_reads_in_minutes_then_hours() {
assert_eq!(format_idle(45), "45s");
assert_eq!(format_idle(1_800), "30m");
assert_eq!(format_idle(3_299), "54m");
assert_eq!(format_idle(3_600), "1h");
assert_eq!(format_idle(4_320), "1h 12m");
assert_eq!(format_idle(93_600), "26h");
}
#[test]
fn test_context_size_is_opt_in() {
let cfg = CacheExpiryConfig {
show_tokens: true,
..Default::default()
};
assert_eq!(
render(&ctx_with(92_000), Some(3_299), &cfg).as_deref(),
Some("Idle: 54m · cache likely cold · 92k context")
);
}
#[test]
fn test_emoji_icon_and_theme_colors() {
let p = Palette::for_theme("github");
let rendered = render_cache_expiry_segment(
&ctx_with(92_000),
Some(3_299),
&CacheExpiryConfig::default(),
IconSet::Emoji,
&p,
)
.unwrap();
assert!(rendered.contains("⏳"));
assert!(rendered.contains(&format!("{}54m{}", p.tokens_alert, p.reset)));
assert!(rendered.contains(&format!("{} · cache likely cold{}", p.dim, p.reset)));
assert!(!rendered.contains("context"));
}
#[test]
fn test_visible_when_listed_and_enabled() {
let cfg = CacheExpiryConfig::default();
assert!(is_visible(&names(&["tokens", "cache_expiry"]), &cfg));
}
#[test]
fn test_hidden_when_not_listed() {
let cfg = CacheExpiryConfig::default();
assert!(!is_visible(&names(&["tokens"]), &cfg));
}
#[test]
fn test_hidden_when_disabled() {
let cfg = CacheExpiryConfig {
enabled: false,
..Default::default()
};
assert!(!is_visible(&names(&["cache_expiry"]), &cfg));
}
}