1mod config;
44mod constraints;
45pub mod glob_detect;
46pub mod location;
47mod parser;
48
49pub use config::{AiGrepConfig, FilePickerConfig, GrepConfig, ParserConfig};
50pub use constraints::{Constraint, GitStatusFilter};
51pub use location::Location;
52pub use parser::{FFFQuery, FuzzyQuery, QueryParser};
53
54pub use smallvec::SmallVec;
56
57pub type ConstraintVec<'a> = SmallVec<[Constraint<'a>; 8]>;
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_empty_query() {
66 let parser = QueryParser::default();
67 let result = parser.parse("");
68 assert!(result.constraints.is_empty());
69 assert_eq!(result.fuzzy_query, FuzzyQuery::Empty);
70 }
71
72 #[test]
73 fn test_whitespace_only() {
74 let parser = QueryParser::default();
75 let result = parser.parse(" ");
76 assert!(result.constraints.is_empty());
77 assert_eq!(result.fuzzy_query, FuzzyQuery::Empty);
78 }
79
80 #[test]
81 fn test_single_token() {
82 let parser = QueryParser::default();
83 let result = parser.parse("hello");
84 assert!(result.constraints.is_empty());
85 assert_eq!(result.fuzzy_query, FuzzyQuery::Text("hello"));
86 }
87
88 #[test]
89 fn test_simple_text() {
90 let parser = QueryParser::default();
91 let result = parser.parse("hello world");
92
93 match &result.fuzzy_query {
94 FuzzyQuery::Parts(parts) => {
95 assert_eq!(parts.len(), 2);
96 assert_eq!(parts[0], "hello");
97 assert_eq!(parts[1], "world");
98 }
99 _ => panic!("Expected Parts fuzzy query"),
100 }
101
102 assert_eq!(result.constraints.len(), 0);
103 }
104
105 #[test]
106 fn test_extension_only() {
107 let parser = QueryParser::default();
108 let result = parser.parse("*.rs");
110 assert!(matches!(result.fuzzy_query, FuzzyQuery::Empty));
111 assert_eq!(result.constraints.len(), 1);
112 assert!(matches!(result.constraints[0], Constraint::Extension("rs")));
113 }
114
115 #[test]
116 fn test_glob_pattern() {
117 let parser = QueryParser::default();
118 let result = parser.parse("**/*.rs foo");
119 assert_eq!(result.constraints.len(), 1);
120 match &result.constraints[0] {
122 Constraint::Glob(pattern) => assert_eq!(*pattern, "**/*.rs"),
123 other => panic!("Expected Glob constraint, got {:?}", other),
124 }
125 }
126
127 #[test]
128 fn test_negation_pattern() {
129 let parser = QueryParser::default();
130 let result = parser.parse("!test foo");
131 assert_eq!(result.constraints.len(), 1);
132 match &result.constraints[0] {
133 Constraint::Not(inner) => {
134 assert!(matches!(**inner, Constraint::Text("test")));
135 }
136 _ => panic!("Expected Not constraint"),
137 }
138 }
139
140 #[test]
141 fn test_path_segment() {
142 let parser = QueryParser::default();
143 let result = parser.parse("/src/ foo");
144 assert_eq!(result.constraints.len(), 1);
145 assert!(matches!(
146 result.constraints[0],
147 Constraint::PathSegment("src")
148 ));
149 }
150
151 #[test]
152 fn test_git_status() {
153 let parser = QueryParser::default();
154 let result = parser.parse("status:modified foo");
155 assert_eq!(result.constraints.len(), 1);
156 assert!(matches!(
157 result.constraints[0],
158 Constraint::GitStatus(GitStatusFilter::Modified)
159 ));
160 }
161
162 #[test]
163 fn test_file_type() {
164 let parser = QueryParser::default();
165 let result = parser.parse("type:rust foo");
166 assert_eq!(result.constraints.len(), 1);
167 assert!(matches!(
168 result.constraints[0],
169 Constraint::FileType("rust")
170 ));
171 }
172
173 #[test]
174 fn test_complex_query() {
175 let parser = QueryParser::default();
176 let result = parser.parse("src name *.rs !test /lib/ status:modified");
177
178 match &result.fuzzy_query {
180 FuzzyQuery::Parts(parts) => {
181 assert_eq!(parts.len(), 2);
182 assert_eq!(parts[0], "src");
183 assert_eq!(parts[1], "name");
184 }
185 _ => panic!("Expected Parts fuzzy query"),
186 }
187
188 assert!(result.constraints.len() >= 4);
190
191 let has_extension = result
193 .constraints
194 .iter()
195 .any(|c| matches!(c, Constraint::Extension("rs")));
196 let has_not = result
197 .constraints
198 .iter()
199 .any(|c| matches!(c, Constraint::Not(_)));
200 let has_path = result
201 .constraints
202 .iter()
203 .any(|c| matches!(c, Constraint::PathSegment("lib")));
204 let has_git_status = result
205 .constraints
206 .iter()
207 .any(|c| matches!(c, Constraint::GitStatus(_)));
208
209 assert!(has_extension, "Should have Extension constraint");
210 assert!(has_not, "Should have Not constraint");
211 assert!(has_path, "Should have PathSegment constraint");
212 assert!(has_git_status, "Should have GitStatus constraint");
213 }
214
215 #[test]
216 fn test_no_heap_allocation_for_small_queries() {
217 let parser = QueryParser::default();
218 let result = parser.parse("*.rs *.toml !test");
219 assert!(!result.constraints.spilled());
221 }
222
223 #[test]
224 fn test_many_fuzzy_parts() {
225 let parser = QueryParser::default();
226 let result = parser.parse("one two three four five six");
227
228 match &result.fuzzy_query {
229 FuzzyQuery::Parts(parts) => {
230 assert_eq!(parts.len(), 6);
231 assert_eq!(parts[0], "one");
232 assert_eq!(parts[5], "six");
233 }
234 _ => panic!("Expected Parts fuzzy query"),
235 }
236 }
237}