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