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