use crate::compat::String;
pub(crate) fn fold(needle: &str) -> String {
needle.to_lowercase()
}
pub(crate) const TYPE_AHEAD_TIMEOUT_MS: u64 = 800;
pub(crate) fn contains(haystack: &str, needle: &str) -> bool {
if needle.is_empty() {
return true;
}
if !needle.is_ascii() {
return haystack.to_lowercase().contains(needle);
}
let needle = needle.as_bytes();
let haystack = haystack.as_bytes();
needle.len() <= haystack.len()
&& haystack.windows(needle.len()).any(|window| window.eq_ignore_ascii_case(needle))
}
pub(crate) fn starts_with(haystack: &str, needle: &str) -> bool {
if needle.is_empty() {
return true;
}
if !needle.is_ascii() {
return haystack.to_lowercase().starts_with(needle);
}
match haystack.as_bytes().get(..needle.len()) {
Some(prefix) => prefix.eq_ignore_ascii_case(needle.as_bytes()),
None => false,
}
}
#[cfg(widgets_unstripped)]
pub(crate) fn ends_with(haystack: &str, needle: &str) -> bool {
if needle.is_empty() {
return true;
}
if !needle.is_ascii() {
return haystack.to_lowercase().ends_with(needle);
}
let haystack = haystack.as_bytes();
match haystack.len().checked_sub(needle.len()) {
Some(start) => haystack[start..].eq_ignore_ascii_case(needle.as_bytes()),
None => false,
}
}
#[cfg(widgets_unstripped)]
pub(crate) fn eq(left: &str, right: &str) -> bool {
if left.len() != right.len() {
return false;
}
if !left.is_ascii() || !right.is_ascii() {
return left.to_lowercase() == right.to_lowercase();
}
left.as_bytes().eq_ignore_ascii_case(right.as_bytes())
}
pub(crate) fn cmp_ignore_ascii_case(left: &str, right: &str) -> core::cmp::Ordering {
if !left.is_ascii() || !right.is_ascii() {
return left.to_lowercase().cmp(&right.to_lowercase());
}
let left = left.as_bytes();
let right = right.as_bytes();
for (left_byte, right_byte) in left.iter().zip(right.iter()) {
match left_byte.to_ascii_lowercase().cmp(&right_byte.to_ascii_lowercase()) {
core::cmp::Ordering::Equal => continue,
other => return other,
}
}
left.len().cmp(&right.len())
}
#[cfg(widgets_unstripped)]
pub(crate) fn contains_word(haystack: &str, needle: &str) -> bool {
if needle.is_empty() {
return true;
}
haystack.split(|ch: char| !ch.is_alphanumeric()).any(|word| eq(word, needle))
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(widgets_unstripped)]
#[test]
fn ascii_comparisons_ignore_case_without_allocating() {
assert!(contains("Hello World", &fold("WORLD")));
assert!(starts_with("Hello", &fold("he")));
assert!(ends_with("Hello", &fold("LO")));
assert!(eq("ABC", &fold("abc")));
assert!(!eq("ABC", &fold("abd")));
assert!(!ends_with("Hello", &fold("hell")));
assert!(contains_word("one two three", &fold("TWO")));
assert!(!contains_word("one two three", &fold("tw")));
}
#[cfg(widgets_unstripped)]
#[test]
fn a_non_ascii_needle_still_matches() {
assert!(contains("Straße", &fold("ß")));
assert!(starts_with("Éclair", &fold("é")));
assert!(eq("Éclair", &fold("éclair")));
}
#[cfg(widgets_unstripped)]
#[test]
fn an_empty_needle_matches_everything() {
assert!(contains("anything", ""));
assert!(starts_with("anything", ""));
assert!(ends_with("anything", ""));
}
#[test]
fn case_insensitive_order_sorts_alphabetically() {
use core::cmp::Ordering;
assert_eq!(cmp_ignore_ascii_case("apple", "Zebra"), Ordering::Less);
assert_eq!(cmp_ignore_ascii_case("Zebra", "apple"), Ordering::Greater);
assert_eq!(cmp_ignore_ascii_case("Apple", "apple"), Ordering::Equal);
assert_eq!(cmp_ignore_ascii_case("app", "apple"), Ordering::Less);
}
}