1use fnv::FnvHashMap;
9use serde::{Deserialize, Serialize};
10use std::num::NonZeroUsize;
11use std::path::Path;
12use std::sync::Arc;
13use syn::spanned::Spanned;
14use syn::{Attribute, ItemEnum, ItemFn, ItemImpl, ItemMacro, ItemStruct, ItemTrait};
15
16use crate::{Item, ItemQuery, ItemType};
17
18#[derive(Debug, Default, Serialize, Deserialize)]
24pub struct SearchIndexMut {
25 pub structs: FnvHashMap<String, Vec<Item>>,
26 pub enums: FnvHashMap<String, Vec<Item>>,
27 pub traits: FnvHashMap<String, Vec<Item>>,
28 pub impl_types: FnvHashMap<String, Vec<Item>>,
29 pub impl_trait_for_types: FnvHashMap<String, Vec<Item>>,
30 pub macros: FnvHashMap<String, Vec<Item>>,
31 pub attribute_macros: FnvHashMap<String, Vec<Item>>,
32 pub functions: FnvHashMap<String, Vec<Item>>,
33 pub type_aliases: FnvHashMap<String, Vec<Item>>,
34}
35
36impl SearchIndexMut {
37 pub fn search(&self, query: &ItemQuery) -> Vec<Item> {
40 let ItemQuery { type_, query, path } = query;
41 let query = query.to_lowercase();
42 let path = path.as_ref().map(|p| p.as_path());
43 match type_ {
44 ItemType::All => {
45 let mut all = Vec::new();
46 all.extend(filter_items(&query, &self.structs, path));
47 all.extend(filter_items(&query, &self.enums, path));
48 all.extend(filter_items(&query, &self.traits, path));
49 all.extend(filter_items(&query, &self.impl_types, path));
50 all.extend(filter_items(&query, &self.impl_trait_for_types, path));
51 all.extend(filter_items(&query, &self.macros, path));
52 all.extend(filter_items(&query, &self.attribute_macros, path));
53 all.extend(filter_items(&query, &self.functions, path));
54 all.extend(filter_items(&query, &self.type_aliases, path));
55 all
56 }
57 ItemType::Struct => filter_items(&query, &self.structs, path),
58 ItemType::Enum => filter_items(&query, &self.enums, path),
59 ItemType::Trait => filter_items(&query, &self.traits, path),
60 ItemType::ImplType => filter_items(&query, &self.impl_types, path),
61 ItemType::ImplTraitForType => filter_items(&query, &self.impl_trait_for_types, path),
62 ItemType::Macro => filter_items(&query, &self.macros, path),
63 ItemType::AttributeMacro => filter_items(&query, &self.attribute_macros, path),
64 ItemType::Function => filter_items(&query, &self.functions, path),
65 ItemType::TypeAlias => filter_items(&query, &self.type_aliases, path),
66 }
67 }
68}
69
70fn filter_items(
73 query: &str,
74 items: &FnvHashMap<String, Vec<Item>>,
75 path: Option<&Path>,
76) -> Vec<Item> {
77 let flatten = items
78 .iter()
79 .filter(|(name, _)| name.contains(&query))
80 .map(|(_, item)| item)
81 .flatten();
82 match path {
83 None => flatten.cloned().collect::<Vec<Item>>(),
84 Some(path) => flatten
85 .filter(|item| item.file.starts_with(path))
86 .cloned()
87 .collect::<Vec<Item>>(),
88 }
89}
90
91pub type SearchIndex = Arc<SearchIndexMut>;
93
94impl SearchIndexMut {
95 pub fn freeze(self) -> SearchIndex {
98 Arc::new(self)
99 }
100}
101
102#[derive(Debug, Default)]
107pub struct SearchIndexBuilder {
108 index: SearchIndexMut,
109}
110
111impl SearchIndexBuilder {
112 pub fn update<P: AsRef<Path>>(&mut self, file: P, content: &str) -> bool {
115 let mut visitor = IndexVisitor::new(&mut self.index, file);
116 if let Ok(ast) = syn::parse_file(content) {
117 syn::visit::visit_file(&mut visitor, &ast);
118 true
119 } else {
120 false
121 }
122 }
123
124 pub fn finish(self) -> SearchIndex {
127 self.index.freeze()
128 }
129}
130
131pub struct IndexVisitor<'i> {
136 index: &'i mut SearchIndexMut,
137 current_file: Arc<Path>,
138}
139
140impl<'i> IndexVisitor<'i> {
141 pub fn new<P: AsRef<Path>>(index: &'i mut SearchIndexMut, current_file: P) -> Self {
144 IndexVisitor {
145 index,
146 current_file: Arc::from(current_file.as_ref()),
147 }
148 }
149
150 fn create_item(
151 &self,
152 name: String,
153 type_: ItemType,
154 item_span: proc_macro2::Span,
155 attrs: &[Attribute],
156 ) -> Item {
157 let mut start_line = item_span.start().line;
159 let end_line = item_span.end().line;
160
161 for attr in attrs {
163 if attr.path().is_ident("doc") {
164 let attr_span = attr.span();
165 start_line = start_line.min(attr_span.start().line);
166 }
167 }
168
169 let start_line = NonZeroUsize::new(start_line).unwrap_or(NonZeroUsize::MIN);
170 let end_line = NonZeroUsize::new(end_line).unwrap_or(NonZeroUsize::MAX);
171
172 Item {
173 name,
174 type_,
175 file: self.current_file.clone(),
176 line_range: start_line..=end_line,
177 }
178 }
179}
180
181impl<'i, 'ast> syn::visit::Visit<'ast> for IndexVisitor<'i> {
182 fn visit_item_enum(&mut self, i: &'ast ItemEnum) {
183 let name = i.ident.to_string();
184 let item = self.create_item(name, ItemType::Enum, i.span(), &i.attrs);
185 self.index
186 .enums
187 .entry(item.name.to_lowercase())
188 .or_default()
189 .push(item);
190 }
191
192 fn visit_item_fn(&mut self, i: &'ast ItemFn) {
193 if is_attribute_macro(&i.attrs) {
194 let name = i.sig.ident.to_string();
195 let item = self.create_item(name, ItemType::AttributeMacro, i.span(), &i.attrs);
196 self.index
197 .attribute_macros
198 .entry(item.name.to_lowercase())
199 .or_default()
200 .push(item);
201 } else {
202 let name = i.sig.ident.to_string();
203 let item = self.create_item(name, ItemType::Function, i.span(), &i.attrs);
204 self.index
205 .functions
206 .entry(item.name.to_lowercase())
207 .or_default()
208 .push(item);
209 }
210 }
211
212 fn visit_item_impl(&mut self, i: &'ast ItemImpl) {
213 let self_ty = &i.self_ty;
214
215 match &i.trait_ {
216 Some((_, path, _)) => {
217 let impl_name = format!(
219 "impl {} for {}",
220 quote::quote! { #path },
221 quote::quote! { #self_ty }
222 );
223 let item =
224 self.create_item(impl_name, ItemType::ImplTraitForType, i.span(), &i.attrs);
225 self.index
226 .impl_trait_for_types
227 .entry(item.name.to_lowercase())
228 .or_default()
229 .push(item);
230 }
231 None => {
232 let impl_name = format!("impl {}", quote::quote! { #self_ty });
234 let item = self.create_item(impl_name, ItemType::ImplType, i.span(), &i.attrs);
235 self.index
236 .impl_types
237 .entry(item.name.to_lowercase())
238 .or_default()
239 .push(item);
240 }
241 };
242 }
243
244 fn visit_item_macro(&mut self, i: &'ast ItemMacro) {
245 if let Some(ident) = &i.ident {
246 let name = ident.to_string();
247 let item = self.create_item(name, ItemType::Macro, i.span(), &i.attrs);
248 self.index
249 .macros
250 .entry(item.name.to_lowercase())
251 .or_default()
252 .push(item);
253 }
254 }
255
256 fn visit_item_struct(&mut self, i: &'ast ItemStruct) {
257 let name = i.ident.to_string();
258 let item = self.create_item(name, ItemType::Struct, i.span(), &i.attrs);
259 self.index
260 .structs
261 .entry(item.name.to_lowercase())
262 .or_default()
263 .push(item);
264 }
265
266 fn visit_item_trait(&mut self, i: &'ast ItemTrait) {
267 let name = i.ident.to_string();
268 let item = self.create_item(name, ItemType::Trait, i.span(), &i.attrs);
269 self.index
270 .traits
271 .entry(item.name.to_lowercase())
272 .or_default()
273 .push(item);
274 }
275
276 fn visit_item_type(&mut self, i: &'ast syn::ItemType) {
277 let name = i.ident.to_string();
278 let item = self.create_item(name, ItemType::TypeAlias, i.span(), &i.attrs);
279 self.index
280 .type_aliases
281 .entry(item.name.to_lowercase())
282 .or_default()
283 .push(item);
284 }
285}
286
287fn is_attribute_macro(attrs: &[Attribute]) -> bool {
288 attrs.iter().any(|attr| {
289 attr.path().is_ident("proc_macro_attribute")
291 })
292}