1mod context;
7#[cfg(feature = "code")]
8mod parser;
9mod symbols;
10#[cfg(feature = "code")]
11mod walker;
12
13pub use context::{CodeContext, ContextBuilder};
14pub use symbols::{CodeChunk, CodeHit, SymbolKind};
15
16use crate::client::Spire;
17use crate::collection::Collection;
18use crate::error::Result;
19use crate::types::IndexResult;
20
21#[derive(Clone)]
23#[allow(dead_code)] pub struct CodeIndex {
25 spire: Spire,
26 name: String,
27 pub(crate) collection: Collection<CodeChunk>,
28}
29
30impl CodeIndex {
31 pub(crate) fn new(spire: Spire, name: String) -> Self {
32 let collection_name = format!("code_{name}");
33 let collection = spire.collection::<CodeChunk>(&collection_name);
34 Self {
35 spire,
36 name,
37 collection,
38 }
39 }
40
41 pub async fn ensure(&self) -> Result<()> {
43 self.collection.ensure().await
44 }
45
46 #[cfg(feature = "code")]
48 pub async fn index_dir(&self, path: &str) -> Result<IndexResult> {
49 let files = walker::walk_dir(path)?;
50 let mut total_chunks = 0;
51 let mut total_symbols = 0;
52 let mut errors = Vec::new();
53
54 for file_path in &files {
55 match self.index_file_internal(file_path).await {
56 Ok((chunks, symbols)) => {
57 total_chunks += chunks;
58 total_symbols += symbols;
59 }
60 Err(e) => {
61 errors.push(format!("{file_path}: {e}"));
62 }
63 }
64 }
65
66 Ok(IndexResult {
67 files: files.len(),
68 chunks: total_chunks,
69 symbols: total_symbols,
70 errors,
71 })
72 }
73
74 #[cfg(feature = "code")]
76 pub async fn index_file(&self, path: &str) -> Result<IndexResult> {
77 let (chunks, symbols) = self.index_file_internal(path).await?;
78 Ok(IndexResult {
79 files: 1,
80 chunks,
81 symbols,
82 errors: Vec::new(),
83 })
84 }
85
86 #[cfg(feature = "code")]
87 async fn index_file_internal(&self, path: &str) -> Result<(usize, usize)> {
88 let content = tokio::fs::read_to_string(path).await?;
89 let language = parser::detect_language(path);
90 let chunks = parser::parse_file(path, &content, &language);
91 let symbols = chunks.iter().filter(|c| c.name.is_some()).count();
92 let count = chunks.len();
93 self.collection.insert_many(&chunks).await?;
94 Ok((count, symbols))
95 }
96
97 pub async fn search(&self, query: &str) -> Result<Vec<CodeHit>> {
99 let hits = self.collection.search(query).limit(10).run().await?;
100 Ok(hits
101 .into_iter()
102 .map(|h| CodeHit {
103 chunk: h.doc,
104 score: h.score,
105 })
106 .collect())
107 }
108
109 pub async fn find_symbol(&self, name: &str) -> Result<Vec<CodeChunk>> {
111 let hits = self.collection.search(name).limit(20).docs().await?;
113 Ok(hits
114 .into_iter()
115 .filter(|c| c.name.as_ref().is_some_and(|n| n.contains(name)))
116 .collect())
117 }
118
119 pub async fn context_for(&self, question: &str) -> Result<CodeContext> {
121 self.context(question).build().await
122 }
123
124 pub fn context(&self, question: &str) -> ContextBuilder<'_> {
126 ContextBuilder::new(self, question.to_string())
127 }
128}