perl_lsp_symbol_query/
lib.rs1#![deny(unsafe_code)]
7#![warn(rust_2018_idioms)]
8#![warn(missing_docs)]
9#![warn(clippy::all)]
10
11use std::cmp::Ordering;
12
13#[must_use]
22pub fn matches_query(name: &str, query: &str) -> bool {
23 if query.is_empty() {
24 return true;
25 }
26
27 let name_lower = name.to_lowercase();
28 let query_lower = query.to_lowercase();
29
30 if name_lower == query_lower {
31 return true;
32 }
33
34 if name_lower.starts_with(&query_lower) {
35 return true;
36 }
37
38 if name_lower.contains(&query_lower) {
39 return true;
40 }
41
42 is_subsequence(&name_lower, &query_lower)
43}
44
45#[must_use]
52pub fn compare_names_by_query(a: &str, b: &str, query: &str) -> Ordering {
53 let query_lower = query.to_lowercase();
54 let a_lower = a.to_lowercase();
55 let b_lower = b.to_lowercase();
56
57 let a_exact = a_lower == query_lower;
58 let b_exact = b_lower == query_lower;
59 let a_prefix = a_lower.starts_with(&query_lower);
60 let b_prefix = b_lower.starts_with(&query_lower);
61
62 match (a_exact, b_exact) {
63 (true, false) => Ordering::Less,
64 (false, true) => Ordering::Greater,
65 _ => match (a_prefix, b_prefix) {
66 (true, false) => Ordering::Less,
67 (false, true) => Ordering::Greater,
68 _ => a.cmp(b),
69 },
70 }
71}
72
73fn is_subsequence(haystack: &str, needle: &str) -> bool {
74 let mut needle_chars = needle.chars();
75 let mut current = needle_chars.next();
76
77 for ch in haystack.chars() {
78 if let Some(target) = current {
79 if ch == target {
80 current = needle_chars.next();
81 }
82 } else {
83 return true;
84 }
85 }
86
87 current.is_none()
88}
89
90#[cfg(test)]
91mod tests {
92 use super::{compare_names_by_query, matches_query};
93
94 #[test]
95 fn query_matching_covers_exact_prefix_contains_and_fuzzy() {
96 assert!(matches_query("foo", "foo"));
97 assert!(matches_query("foobar", "foo"));
98 assert!(matches_query("foobar", "bar"));
99 assert!(matches_query("foobar", "fb"));
100 assert!(!matches_query("alpha", "zq"));
101 }
102
103 #[test]
104 fn empty_query_matches_anything() {
105 assert!(matches_query("anything", ""));
106 }
107
108 #[test]
109 fn relevance_prefers_exact_then_prefix_then_name_order() {
110 let mut names = ["foxtrot", "foo", "foobar", "alpha"];
111 names.sort_by(|a, b| compare_names_by_query(a, b, "foo"));
112
113 assert_eq!(names, ["foo", "foobar", "alpha", "foxtrot"]);
114 }
115}