Skip to main content

reflex/query/
filter.rs

1//! Query filter types and stateless filtering helpers
2
3use regex::Regex;
4
5use crate::models::SymbolKind;
6
7/// Query filter options
8#[derive(Debug, Clone)]
9pub struct QueryFilter {
10    /// Language filter (None = all languages)
11    pub language: Option<crate::models::Language>,
12    /// Symbol kind filter (None = all kinds)
13    pub kind: Option<SymbolKind>,
14    /// Use AST pattern matching (vs lexical search)
15    pub use_ast: bool,
16    /// Use regex pattern matching
17    pub use_regex: bool,
18    /// Maximum number of results
19    pub limit: Option<usize>,
20    /// Search symbol definitions only (vs full-text)
21    pub symbols_mode: bool,
22    /// Show full symbol body (from span.start_line to span.end_line)
23    pub expand: bool,
24    /// File path filter (substring match)
25    pub file_pattern: Option<String>,
26    /// Exact symbol name match (no substring matching)
27    pub exact: bool,
28    /// Use substring matching instead of word-boundary matching (opt-in, expansive)
29    pub use_contains: bool,
30    /// Query timeout in seconds (0 = no timeout)
31    pub timeout_secs: u64,
32    /// Glob patterns to include (empty = all files)
33    pub glob_patterns: Vec<String>,
34    /// Glob patterns to exclude (applied after includes)
35    pub exclude_patterns: Vec<String>,
36    /// Return only unique file paths (deduplicated)
37    pub paths_only: bool,
38    /// Pagination offset (skip first N results after sorting)
39    pub offset: Option<usize>,
40    /// Force execution of potentially expensive queries (bypass broad query detection)
41    pub force: bool,
42    /// Suppress warning/info output (for --json mode to ensure pure JSON output)
43    pub suppress_output: bool,
44    /// Include dependency information in results
45    pub include_dependencies: bool,
46    /// Test-only: Override large index threshold (None = use default of 20,000)
47    #[doc(hidden)]
48    pub test_large_index_threshold: Option<usize>,
49    /// Test-only: Override short pattern threshold (None = use default of 4)
50    #[doc(hidden)]
51    pub test_short_pattern_threshold: Option<usize>,
52}
53
54impl Default for QueryFilter {
55    fn default() -> Self {
56        Self {
57            language: None,
58            kind: None,
59            use_ast: false,
60            use_regex: false,
61            limit: Some(100),  // Default: limit to 100 results for token efficiency
62            symbols_mode: false,
63            expand: false,
64            file_pattern: None,
65            exact: false,
66            use_contains: false,  // Default: word-boundary matching
67            timeout_secs: 30, // 30 seconds default timeout
68            glob_patterns: Vec::new(),
69            exclude_patterns: Vec::new(),
70            paths_only: false,
71            offset: None,
72            force: false,  // Default: enable broad query detection
73            suppress_output: false,  // Default: show warnings/info
74            include_dependencies: false,  // Default: don't load dependencies for performance
75            test_large_index_threshold: None,  // Default: use production threshold (20,000)
76            test_short_pattern_threshold: None,  // Default: use production threshold (4)
77        }
78    }
79}
80
81/// Map a language keyword to its corresponding SymbolKind.
82///
83/// When users search for keywords like "class" or "function" with --symbols,
84/// automatically infer the kind filter to return only symbols of that type.
85pub fn keyword_to_kind(keyword: &str) -> Option<SymbolKind> {
86    match keyword {
87        "class" => Some(SymbolKind::Class),
88        "struct" => Some(SymbolKind::Struct),
89        "enum" => Some(SymbolKind::Enum),
90        "interface" => Some(SymbolKind::Interface),
91        "trait" => Some(SymbolKind::Trait),
92        "type" => Some(SymbolKind::Type),
93        "record" => Some(SymbolKind::Struct),  // C# record types
94        "function" | "fn" | "def" | "func" => Some(SymbolKind::Function),
95        "const" | "static" => Some(SymbolKind::Constant),
96        "var" | "let" => Some(SymbolKind::Variable),
97        "mod" | "module" | "namespace" => Some(SymbolKind::Module),
98        "impl" | "async" => None,
99        _ => None,
100    }
101}
102
103/// Check if pattern appears at word boundaries in a line.
104///
105/// Used for default (restrictive) matching to find complete identifiers
106/// rather than substrings.
107pub fn has_word_boundary_match(line: &str, pattern: &str) -> bool {
108    let escaped_pattern = regex::escape(pattern);
109    let pattern_with_boundaries = format!(r"\b{}\b", escaped_pattern);
110
111    if let Ok(re) = Regex::new(&pattern_with_boundaries) {
112        re.is_match(line)
113    } else {
114        log::debug!("Word boundary regex failed for pattern '{}', falling back to substring", pattern);
115        line.contains(pattern)
116    }
117}