use crate::cache::AnalysisCache;
use ignore::WalkBuilder;
use std::path::Path;
use tracing::instrument;
#[instrument(skip_all, fields(prefix = %prefix))]
pub fn path_completions(root: &Path, prefix: &str) -> Vec<String> {
if prefix.is_empty() {
return Vec::new();
}
let (search_dir, name_prefix) = if let Some(last_slash) = prefix.rfind('/') {
let dir_part = &prefix[..=last_slash];
let name_part = &prefix[last_slash + 1..];
let full_path = root.join(dir_part);
(full_path, name_part.to_string())
} else {
(root.to_path_buf(), prefix.to_string())
};
if !search_dir.exists() {
return Vec::new();
}
let mut results = Vec::new();
let mut builder = WalkBuilder::new(&search_dir);
builder
.hidden(true)
.standard_filters(true)
.max_depth(Some(1));
for result in builder.build() {
if results.len() >= 100 {
break;
}
let Ok(entry) = result else { continue };
let path = entry.path();
if path == search_dir {
continue;
}
if let Some(file_name) = path.file_name().and_then(|n| n.to_str())
&& file_name.starts_with(&name_prefix)
{
if let Ok(rel_path) = path.strip_prefix(root) {
let rel_str = rel_path.to_string_lossy().to_string();
results.push(rel_str);
}
}
}
results
}
#[instrument(skip(cache), fields(path = %path.display(), prefix = %prefix))]
pub fn symbol_completions(cache: &AnalysisCache, path: &Path, prefix: &str) -> Vec<String> {
if prefix.is_empty() {
return Vec::new();
}
let cache_key = match std::fs::metadata(path) {
Ok(meta) => match meta.modified() {
Ok(mtime) => crate::cache::CacheKey {
path: path.to_path_buf(),
modified: mtime,
mode: crate::types::AnalysisMode::FileDetails,
},
Err(_) => return Vec::new(),
},
Err(_) => return Vec::new(),
};
let Some(cached) = cache.get(&cache_key) else {
return Vec::new();
};
let mut results = Vec::new();
for func in &cached.semantic.functions {
if results.len() >= 100 {
break;
}
if func.name.starts_with(prefix) {
results.push(func.name.clone());
}
}
for class in &cached.semantic.classes {
if results.len() >= 100 {
break;
}
if class.name.starts_with(prefix) {
results.push(class.name.clone());
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_path_completions_slash_prefix() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::create_dir(root.join("src")).unwrap();
fs::write(root.join("src/main.rs"), "fn main() {}").unwrap();
let results = path_completions(root, "src/ma");
assert!(
results.iter().any(|r| r.contains("main.rs")),
"expected 'main.rs' in completions, got {:?}",
results
);
}
#[test]
fn test_path_completions_empty_prefix() {
let temp = TempDir::new().unwrap();
let results = path_completions(temp.path(), "");
assert!(
results.is_empty(),
"expected empty results for empty prefix"
);
}
}