use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct QueryHistory {
queries: VecDeque<HistoricalQuery>,
max_size: usize,
cursor: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HistoricalQuery {
pub pattern: String,
pub timestamp: String,
pub filters: QueryFilters,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct QueryFilters {
pub symbols_mode: bool,
pub regex_mode: bool,
pub language: Option<String>,
pub kind: Option<String>,
pub glob_patterns: Vec<String>,
pub exclude_patterns: Vec<String>,
pub expand: bool,
pub contains: bool,
}
impl QueryHistory {
pub fn new(max_size: usize) -> Self {
Self {
queries: VecDeque::new(),
max_size,
cursor: None,
}
}
pub fn load() -> Result<Self> {
let path = Self::history_path()?;
if !path.exists() {
return Ok(Self::new(1000));
}
let content = fs::read_to_string(&path)?;
let queries: Vec<HistoricalQuery> = serde_json::from_str(&content)?;
Ok(Self {
queries: queries.into(),
max_size: 1000,
cursor: None,
})
}
pub fn save(&self) -> Result<()> {
let path = Self::history_path()?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let queries: Vec<_> = self.queries.iter().cloned().collect();
let content = serde_json::to_string_pretty(&queries)?;
fs::write(&path, content)?;
Ok(())
}
pub fn add(&mut self, pattern: String, filters: QueryFilters) {
if pattern.trim().is_empty() {
return;
}
let query = HistoricalQuery {
pattern,
timestamp: chrono::Utc::now().to_rfc3339(),
filters,
};
self.queries.retain(|q| q.pattern != query.pattern || q.filters != query.filters);
self.queries.push_front(query);
while self.queries.len() > self.max_size {
self.queries.pop_back();
}
self.cursor = None;
}
pub fn prev(&mut self) -> Option<&HistoricalQuery> {
if self.queries.is_empty() {
return None;
}
let new_cursor = match self.cursor {
None => 0,
Some(pos) => {
if pos + 1 < self.queries.len() {
pos + 1
} else {
pos
}
}
};
self.cursor = Some(new_cursor);
self.queries.get(new_cursor)
}
pub fn next(&mut self) -> Option<&HistoricalQuery> {
match self.cursor {
None => None,
Some(0) => {
self.cursor = None;
None
}
Some(pos) => {
let new_cursor = pos - 1;
self.cursor = Some(new_cursor);
self.queries.get(new_cursor)
}
}
}
pub fn reset_cursor(&mut self) {
self.cursor = None;
}
pub fn len(&self) -> usize {
self.queries.len()
}
pub fn is_empty(&self) -> bool {
self.queries.is_empty()
}
fn history_path() -> Result<PathBuf> {
let home = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
Ok(home.join(".reflex").join("interactive_history.json"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_history_creation() {
let history = QueryHistory::new(100);
assert_eq!(history.len(), 0);
assert!(history.is_empty());
}
#[test]
fn test_add_query() {
let mut history = QueryHistory::new(100);
history.add("test".to_string(), QueryFilters::default());
assert_eq!(history.len(), 1);
history.add("another".to_string(), QueryFilters::default());
assert_eq!(history.len(), 2);
}
#[test]
fn test_deduplication() {
let mut history = QueryHistory::new(100);
history.add("test".to_string(), QueryFilters::default());
history.add("test".to_string(), QueryFilters::default());
assert_eq!(history.len(), 1);
}
#[test]
fn test_max_size() {
let mut history = QueryHistory::new(3);
history.add("one".to_string(), QueryFilters::default());
history.add("two".to_string(), QueryFilters::default());
history.add("three".to_string(), QueryFilters::default());
history.add("four".to_string(), QueryFilters::default());
assert_eq!(history.len(), 3);
}
#[test]
fn test_navigation() {
let mut history = QueryHistory::new(100);
history.add("one".to_string(), QueryFilters::default());
history.add("two".to_string(), QueryFilters::default());
history.add("three".to_string(), QueryFilters::default());
assert_eq!(history.prev().unwrap().pattern, "three");
assert_eq!(history.prev().unwrap().pattern, "two");
assert_eq!(history.prev().unwrap().pattern, "one");
assert_eq!(history.next().unwrap().pattern, "two");
assert_eq!(history.next().unwrap().pattern, "three");
assert!(history.next().is_none());
}
}