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