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    /// Number of context lines to show before and after each match (default: 0 = disabled)
47    pub context_lines: usize,
48    /// Test-only: Override large index threshold (None = use default of 20,000)
49    #[doc(hidden)]
50    pub test_large_index_threshold: Option<usize>,
51    /// Test-only: Override short pattern threshold (None = use default of 4)
52    #[doc(hidden)]
53    pub test_short_pattern_threshold: Option<usize>,
54}
55
56impl Default for QueryFilter {
57    fn default() -> Self {
58        Self {
59            language: None,
60            kind: None,
61            use_ast: false,
62            use_regex: false,
63            limit: Some(100), // Default: limit to 100 results for token efficiency
64            symbols_mode: false,
65            expand: false,
66            file_pattern: None,
67            exact: false,
68            use_contains: false, // Default: word-boundary matching
69            timeout_secs: 30,    // 30 seconds default timeout
70            glob_patterns: Vec::new(),
71            exclude_patterns: Vec::new(),
72            paths_only: false,
73            offset: None,
74            force: false,                       // Default: enable broad query detection
75            suppress_output: false,             // Default: show warnings/info
76            include_dependencies: false,        // Default: don't load dependencies for performance
77            context_lines: 0,                   // Default: no context lines shown
78            test_large_index_threshold: None,   // Default: use production threshold (20,000)
79            test_short_pattern_threshold: None, // Default: use production threshold (4)
80        }
81    }
82}
83
84/// Map a language keyword to its corresponding SymbolKind.
85///
86/// When users search for keywords like "class" or "function" with --symbols,
87/// automatically infer the kind filter to return only symbols of that type.
88pub fn keyword_to_kind(keyword: &str) -> Option<SymbolKind> {
89    match keyword {
90        "class" => Some(SymbolKind::Class),
91        "struct" => Some(SymbolKind::Struct),
92        "enum" => Some(SymbolKind::Enum),
93        "interface" => Some(SymbolKind::Interface),
94        "trait" => Some(SymbolKind::Trait),
95        "type" => Some(SymbolKind::Type),
96        "record" => Some(SymbolKind::Struct), // C# record types
97        "function" | "fn" | "def" | "func" => Some(SymbolKind::Function),
98        "const" | "static" => Some(SymbolKind::Constant),
99        "var" | "let" => Some(SymbolKind::Variable),
100        "mod" | "module" | "namespace" => Some(SymbolKind::Module),
101        "impl" | "async" => None,
102        _ => None,
103    }
104}
105
106/// Check if pattern appears at word boundaries in a line.
107///
108/// Used for default (restrictive) matching to find complete identifiers
109/// rather than substrings.
110pub fn has_word_boundary_match(line: &str, pattern: &str) -> bool {
111    let escaped_pattern = regex::escape(pattern);
112    let pattern_with_boundaries = format!(r"\b{}\b", escaped_pattern);
113
114    if let Ok(re) = Regex::new(&pattern_with_boundaries) {
115        re.is_match(line)
116    } else {
117        log::debug!(
118            "Word boundary regex failed for pattern '{}', falling back to substring",
119            pattern
120        );
121        line.contains(pattern)
122    }
123}