use anyhow::{Context, Result};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::Mutex;
use tree_sitter::Tree;
lazy_static::lazy_static! {
static ref TREE_CACHE: Mutex<HashMap<String, (Tree, u64)>> = Mutex::new(HashMap::new());
static ref CACHE_HITS: Mutex<usize> = Mutex::new(0);
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
fn compute_content_hash(content: &str) -> u64 {
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
pub fn get_or_parse_tree(
file_path: &str,
content: &str,
parser: &mut tree_sitter::Parser,
) -> Result<Tree> {
let content_hash = compute_content_hash(content);
let debug_mode = std::env::var("DEBUG").unwrap_or_default() == "1";
{
let mut cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if let Some((cached_tree, cached_hash)) = cache.get(file_path) {
if cached_hash == &content_hash {
{
let mut hits = CACHE_HITS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*hits += 1;
}
if debug_mode {
println!("[DEBUG] Cache hit for file: {file_path}");
}
return Ok(cached_tree.clone());
} else {
cache.remove(file_path);
if debug_mode {
println!("[DEBUG] Cache invalidated for file: {file_path} (content changed)");
}
}
} else if debug_mode {
println!("[DEBUG] Cache miss for file: {file_path}");
}
}
let tree = parser
.parse(content, None)
.context(format!("Failed to parse file: {file_path}"))?;
{
let mut cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
cache.insert(file_path.to_string(), (tree.clone(), content_hash));
if debug_mode {
println!("[DEBUG] Cached parsed tree for file: {file_path}");
println!("[DEBUG] Current cache size: {} entries", cache.len());
}
}
Ok(tree)
}
#[allow(dead_code)]
pub fn clear_tree_cache() {
let mut cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let debug_mode = std::env::var("DEBUG").unwrap_or_default() == "1";
if debug_mode {
println!("[DEBUG] Clearing tree cache ({} entries)", cache.len());
}
cache.clear();
let mut hits = CACHE_HITS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*hits = 0;
}
#[allow(dead_code)]
pub fn invalidate_cache_entry(file_path: &str) {
let mut cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let debug_mode = std::env::var("DEBUG").unwrap_or_default() == "1";
if cache.remove(file_path).is_some() && debug_mode {
println!("[DEBUG] Removed file from cache: {file_path}");
}
}
#[allow(dead_code)]
pub fn acquire_test_mutex() -> std::sync::MutexGuard<'static, ()> {
TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[allow(dead_code)]
pub fn get_cache_size() -> usize {
let cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
cache.len()
}
#[allow(dead_code)]
pub fn is_in_cache(file_path: &str) -> bool {
let cache = TREE_CACHE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
cache.contains_key(file_path)
}
#[allow(dead_code)]
pub fn reset_cache_hit_counter() {
let mut hits = CACHE_HITS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*hits = 0;
}
#[allow(dead_code)]
pub fn get_cache_hit_count() -> usize {
let hits = CACHE_HITS
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
*hits
}