use crate::pattern::CaseOptions;
use crate::query::UsageContext;
use crate::Pattern;
use crate::SymbolKind;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum SortOrder {
#[default]
Refs,
Alpha,
Impls,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TypeFilter {
pub return_type: Option<Pattern>,
pub param_type: Option<Pattern>,
pub field_type: Option<Pattern>,
pub uses_type: Option<Pattern>,
pub has_bound: Option<Pattern>,
}
impl TypeFilter {
pub fn returns(pattern: impl Into<String>) -> Self {
Self {
return_type: Some(Pattern::glob(pattern.into())),
..Default::default()
}
}
pub fn has_param(pattern: impl Into<String>) -> Self {
Self {
param_type: Some(Pattern::glob(pattern.into())),
..Default::default()
}
}
pub fn has_field(pattern: impl Into<String>) -> Self {
Self {
field_type: Some(Pattern::glob(pattern.into())),
..Default::default()
}
}
pub fn uses(pattern: impl Into<String>) -> Self {
Self {
uses_type: Some(Pattern::glob(pattern.into())),
..Default::default()
}
}
pub fn with_bound(mut self, pattern: impl Into<String>) -> Self {
self.has_bound = Some(Pattern::glob(pattern.into()));
self
}
pub fn is_empty(&self) -> bool {
self.return_type.is_none()
&& self.param_type.is_none()
&& self.field_type.is_none()
&& self.uses_type.is_none()
&& self.has_bound.is_none()
}
pub fn expected_contexts(&self) -> Vec<UsageContext> {
let mut contexts = Vec::new();
if self.return_type.is_some() {
contexts.push(UsageContext::ReturnType);
}
if self.param_type.is_some() {
contexts.push(UsageContext::ParamType);
}
if self.field_type.is_some() {
contexts.push(UsageContext::FieldType);
contexts.push(UsageContext::VariantField);
}
contexts
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveryQuery {
pub pattern: Pattern,
#[serde(default, skip_serializing_if = "is_default_case_options")]
pub case_options: CaseOptions,
pub kinds: Option<Vec<SymbolKind>>,
pub in_crate: Option<String>,
pub in_module: Option<String>,
pub include_relations: bool,
pub relation_depth: usize,
pub limit: Option<usize>,
pub sort: SortOrder,
pub type_filter: Option<TypeFilter>,
}
fn is_default_case_options(opts: &CaseOptions) -> bool {
opts.is_default()
}
impl DiscoveryQuery {
pub fn symbol(pattern: impl Into<String>) -> Self {
Self {
pattern: Pattern::glob(pattern.into()),
case_options: CaseOptions::default(),
kinds: None,
in_crate: None,
in_module: None,
include_relations: false,
relation_depth: 1,
limit: None,
sort: SortOrder::default(),
type_filter: None,
}
}
pub fn symbol_with_options(pattern: impl Into<String>, case_options: CaseOptions) -> Self {
Self {
pattern: Pattern::glob_with_options(pattern.into(), case_options),
case_options,
kinds: None,
in_crate: None,
in_module: None,
include_relations: false,
relation_depth: 1,
limit: None,
sort: SortOrder::default(),
type_filter: None,
}
}
pub fn exact(name: impl Into<String>) -> Self {
Self {
pattern: Pattern::exact(name.into()),
case_options: CaseOptions::default(),
kinds: None,
in_crate: None,
in_module: None,
include_relations: false,
relation_depth: 1,
limit: None,
sort: SortOrder::default(),
type_filter: None,
}
}
pub fn regex(pattern: impl Into<String>) -> Result<Self, crate::pattern::PatternError> {
Ok(Self {
pattern: Pattern::regex(pattern.into())?,
case_options: CaseOptions::default(),
kinds: None,
in_crate: None,
in_module: None,
include_relations: false,
relation_depth: 1,
limit: None,
sort: SortOrder::default(),
type_filter: None,
})
}
pub fn ignore_case(mut self) -> Self {
self.case_options.ignore_case = true;
let pattern_str = self.pattern.as_str().to_string();
self.pattern = if self.pattern.is_glob() {
Pattern::glob_with_options(pattern_str, self.case_options)
} else if self.pattern.is_regex() {
Pattern::regex_with_options(pattern_str, self.case_options)
.unwrap_or_else(|_| self.pattern.clone())
} else {
Pattern::exact_with_options(pattern_str, self.case_options)
};
self
}
pub fn ignore_word_separate(mut self) -> Self {
self.case_options.ignore_word_separate = true;
let pattern_str = self.pattern.as_str().to_string();
self.pattern = if self.pattern.is_glob() {
Pattern::glob_with_options(pattern_str, self.case_options)
} else if self.pattern.is_regex() {
self.pattern.clone()
} else {
Pattern::exact_with_options(pattern_str, self.case_options)
};
self
}
pub fn kinds(mut self, kinds: Vec<SymbolKind>) -> Self {
self.kinds = Some(kinds);
self
}
pub fn kind(mut self, kind: SymbolKind) -> Self {
self.kinds = Some(vec![kind]);
self
}
pub fn in_crate(mut self, crate_name: impl Into<String>) -> Self {
self.in_crate = Some(crate_name.into());
self
}
pub fn in_module(mut self, module: impl Into<String>) -> Self {
self.in_module = Some(module.into());
self
}
pub fn with_relations(mut self) -> Self {
self.include_relations = true;
self
}
pub fn relation_depth(mut self, depth: usize) -> Self {
self.relation_depth = depth;
self
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = Some(limit);
self
}
pub fn sort(mut self, sort: SortOrder) -> Self {
self.sort = sort;
self
}
pub fn with_type_filter(mut self, filter: TypeFilter) -> Self {
self.type_filter = Some(filter);
self
}
pub fn returns(self, pattern: impl Into<String>) -> Self {
self.with_type_filter(TypeFilter::returns(pattern))
}
pub fn has_param_type(self, pattern: impl Into<String>) -> Self {
self.with_type_filter(TypeFilter::has_param(pattern))
}
pub fn has_field_type(self, pattern: impl Into<String>) -> Self {
self.with_type_filter(TypeFilter::has_field(pattern))
}
}
impl Default for DiscoveryQuery {
fn default() -> Self {
Self::symbol("*")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_symbol_query() {
let query = DiscoveryQuery::symbol("*Config");
assert!(query.pattern.matches("AppConfig"));
assert!(query.kinds.is_none());
}
#[test]
fn test_exact_query() {
let query = DiscoveryQuery::exact("Config");
assert!(query.pattern.matches("Config"));
assert!(!query.pattern.matches("AppConfig"));
}
#[test]
fn test_query_with_kinds() {
let query = DiscoveryQuery::symbol("*").kinds(vec![SymbolKind::Struct, SymbolKind::Enum]);
assert_eq!(query.kinds.as_ref().unwrap().len(), 2);
}
#[test]
fn test_query_chaining() {
let query = DiscoveryQuery::symbol("*Handler")
.kind(SymbolKind::Struct)
.in_crate("mylib")
.in_module("handlers")
.with_relations()
.limit(10);
assert!(query.kinds.is_some());
assert_eq!(query.in_crate, Some("mylib".to_string()));
assert_eq!(query.in_module, Some("handlers".to_string()));
assert!(query.include_relations);
assert_eq!(query.limit, Some(10));
}
}