use std::time::{Duration, Instant};
use ratatui::text::Line;
use super::{
composer_pointer::ComposerHit,
file_picker::{FilePaletteMatches, PathTokenSource, WorkspacePathCache},
types::CommandChoice,
App, ComposerMode,
};
pub(super) const PALETTE_CACHE_TTL: Duration = Duration::from_secs(2);
#[derive(Clone, Debug)]
struct FileMatchCache {
source: PathTokenSource,
query: String,
matches: FilePaletteMatches,
refreshed_at: Instant,
}
pub(super) struct DiscoveryCache<T> {
value: std::sync::Arc<T>,
refreshed_at: Instant,
}
impl<T> DiscoveryCache<T> {
pub(super) fn get_or_refresh(
slot: &mut Option<Self>,
ttl: Duration,
discover: impl FnOnce() -> T,
) -> std::sync::Arc<T> {
if let Some(cache) = slot
.as_ref()
.filter(|cache| cache.refreshed_at.elapsed() < ttl)
{
return std::sync::Arc::clone(&cache.value);
}
let value = std::sync::Arc::new(discover());
*slot = Some(Self {
value: std::sync::Arc::clone(&value),
refreshed_at: Instant::now(),
});
value
}
}
#[derive(Default)]
pub(super) struct PaletteCaches {
file: Option<FileMatchCache>,
pub(super) skills: Option<DiscoveryCache<Vec<crate::skills::Skill>>>,
pub(super) prompt_templates: Option<DiscoveryCache<crate::prompt_templates::PromptTemplates>>,
workspace: WorkspacePathCache,
}
impl PaletteCaches {
pub(super) fn fresh_file(
&self,
source: PathTokenSource,
query: &str,
ttl: Duration,
) -> Option<FilePaletteMatches> {
let cache = self.file.as_ref()?;
(cache.source == source && cache.query == query && cache.refreshed_at.elapsed() < ttl)
.then(|| cache.matches.clone())
}
pub(super) fn store_file(
&mut self,
source: PathTokenSource,
query: String,
matches: FilePaletteMatches,
) {
self.file = Some(FileMatchCache {
source,
query,
matches,
refreshed_at: Instant::now(),
});
}
pub(super) fn clear_file(&mut self) {
self.file = None;
}
pub(super) fn workspace_mut(&mut self) -> &mut WorkspacePathCache {
&mut self.workspace
}
#[cfg(test)]
pub(super) fn expire_prompt_templates(&mut self) {
if let Some(cache) = self.prompt_templates.as_mut() {
cache.refreshed_at = Instant::now() - PALETTE_CACHE_TTL;
}
}
#[cfg(test)]
pub(super) fn expire_file(&mut self) {
if let Some(cache) = self.file.as_mut() {
cache.refreshed_at = Instant::now() - PALETTE_CACHE_TTL;
}
}
#[cfg(test)]
pub(super) fn expire_workspace(&mut self) {
self.workspace.expire();
}
}
#[derive(Debug)]
pub(super) enum ActivePalette {
Command(Vec<CommandChoice>),
File(FilePaletteMatches),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum PaletteRow {
Command(usize),
File(usize),
}
#[derive(Debug, Default)]
pub(super) struct PaletteFrame {
pub(super) lines: Vec<Line<'static>>,
pub(super) hits: Vec<ComposerHit<PaletteRow>>,
}
impl PaletteFrame {
pub(super) fn push_row(&mut self, line: Line<'static>, row: PaletteRow, selected: bool) {
let index = self.lines.len();
let hit = ComposerHit::rows(index..index + 1, row);
self.hits.push(hit.with_active(selected));
self.lines.push(line);
}
pub(super) fn push_line(&mut self, line: Line<'static>) {
self.lines.push(line);
}
}
impl App {
pub(super) fn active_palette(&mut self) -> Option<ActivePalette> {
if !matches!(self.input_ui.composer(), ComposerMode::Input) {
return None;
}
if self.input_ui.shell_mode().is_none() {
if let Some(matches) = self.visible_command_matches() {
return Some(ActivePalette::Command(matches));
}
}
if self.input_ui.file_palette_dismissed() {
return None;
}
let matches = self.file_match_list();
(!matches.is_empty()).then_some(ActivePalette::File(matches))
}
#[cfg(test)]
pub(super) fn command_palette_visible(&mut self) -> bool {
matches!(self.active_palette(), Some(ActivePalette::Command(_)))
}
}