code_analyze_mcp/
completion.rs1use crate::cache::AnalysisCache;
2use ignore::WalkBuilder;
3use std::path::Path;
4use tracing::instrument;
5
6#[instrument(skip_all, fields(prefix = %prefix))]
10pub fn path_completions(root: &Path, prefix: &str) -> Vec<String> {
11 if prefix.is_empty() {
12 return Vec::new();
13 }
14
15 let (search_dir, name_prefix) = if let Some(last_slash) = prefix.rfind('/') {
17 let dir_part = &prefix[..=last_slash];
18 let name_part = &prefix[last_slash + 1..];
19 let full_path = root.join(dir_part);
20 (full_path, name_part.to_string())
21 } else {
22 (root.to_path_buf(), prefix.to_string())
23 };
24
25 if !search_dir.exists() {
27 return Vec::new();
28 }
29
30 let mut results = Vec::new();
31
32 let mut builder = WalkBuilder::new(&search_dir);
34 builder
35 .hidden(true)
36 .standard_filters(true)
37 .max_depth(Some(1));
38
39 for result in builder.build() {
40 if results.len() >= 100 {
41 break;
42 }
43
44 match result {
45 Ok(entry) => {
46 let path = entry.path();
47 if path == search_dir {
49 continue;
50 }
51
52 if let Some(file_name) = path.file_name().and_then(|n| n.to_str())
54 && file_name.starts_with(&name_prefix)
55 {
56 if let Ok(rel_path) = path.strip_prefix(root) {
58 let rel_str = rel_path.to_string_lossy().to_string();
59 results.push(rel_str);
60 }
61 }
62 }
63 Err(_) => {
64 continue;
66 }
67 }
68 }
69
70 results
71}
72
73#[instrument(skip(cache), fields(path = %path.display(), prefix = %prefix))]
77pub fn symbol_completions(cache: &AnalysisCache, path: &Path, prefix: &str) -> Vec<String> {
78 if prefix.is_empty() {
79 return Vec::new();
80 }
81
82 let cache_key = match std::fs::metadata(path) {
84 Ok(meta) => match meta.modified() {
85 Ok(mtime) => crate::cache::CacheKey {
86 path: path.to_path_buf(),
87 modified: mtime,
88 mode: crate::types::AnalysisMode::FileDetails,
89 },
90 Err(_) => return Vec::new(),
91 },
92 Err(_) => return Vec::new(),
93 };
94
95 let cached = match cache.get(&cache_key) {
97 Some(output) => output,
98 None => return Vec::new(),
99 };
100
101 let mut results = Vec::new();
102
103 for func in &cached.semantic.functions {
105 if results.len() >= 100 {
106 break;
107 }
108 if func.name.starts_with(prefix) {
109 results.push(func.name.clone());
110 }
111 }
112
113 for class in &cached.semantic.classes {
115 if results.len() >= 100 {
116 break;
117 }
118 if class.name.starts_with(prefix) {
119 results.push(class.name.clone());
120 }
121 }
122
123 results
124}