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