Skip to main content

code_analyze_mcp/
completion.rs

1use crate::cache::AnalysisCache;
2use ignore::WalkBuilder;
3use std::path::Path;
4use tracing::instrument;
5
6/// Get path completions for a given prefix within a root directory.
7/// Uses ignore crate with standard filters to respect .gitignore.
8/// Returns matching file and directory paths up to 100 results.
9#[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    // Determine the search directory and filename prefix
16    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 directory doesn't exist, return empty
26    if !search_dir.exists() {
27        return Vec::new();
28    }
29
30    let mut results = Vec::new();
31
32    // Walk with depth 1 to get immediate children
33    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                // Skip the root directory itself
48                if path == search_dir {
49                    continue;
50                }
51
52                // Get the filename
53                if let Some(file_name) = path.file_name().and_then(|n| n.to_str())
54                    && file_name.starts_with(&name_prefix)
55                {
56                    // Construct relative path from root
57                    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                // Skip unreadable entries
65                continue;
66            }
67        }
68    }
69
70    results
71}
72
73/// Get symbol completions (function and class names) for a given file path.
74/// Looks up cached FileAnalysisOutput and extracts matching symbols.
75/// Returns matching function and class names up to 100 results.
76#[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    // Get file metadata for cache key
83    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    // Look up in cache
96    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    // Extract function names matching prefix
104    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    // Extract class names matching prefix
114    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}