strings.rs

  1use crate::{
  2    matcher::{Match, MatchCandidate, Matcher},
  3    CharBag,
  4};
  5use gpui2::BackgroundExecutor;
  6use std::{
  7    borrow::Cow,
  8    cmp::{self, Ordering},
  9    sync::atomic::AtomicBool,
 10};
 11
 12#[derive(Clone, Debug)]
 13pub struct StringMatchCandidate {
 14    pub id: usize,
 15    pub string: String,
 16    pub char_bag: CharBag,
 17}
 18
 19impl Match for StringMatch {
 20    fn score(&self) -> f64 {
 21        self.score
 22    }
 23
 24    fn set_positions(&mut self, positions: Vec<usize>) {
 25        self.positions = positions;
 26    }
 27}
 28
 29impl StringMatchCandidate {
 30    pub fn new(id: usize, string: String) -> Self {
 31        Self {
 32            id,
 33            char_bag: CharBag::from(string.as_str()),
 34            string,
 35        }
 36    }
 37}
 38
 39impl<'a> MatchCandidate for &'a StringMatchCandidate {
 40    fn has_chars(&self, bag: CharBag) -> bool {
 41        self.char_bag.is_superset(bag)
 42    }
 43
 44    fn to_string(&self) -> Cow<'a, str> {
 45        self.string.as_str().into()
 46    }
 47}
 48
 49#[derive(Clone, Debug)]
 50pub struct StringMatch {
 51    pub candidate_id: usize,
 52    pub score: f64,
 53    pub positions: Vec<usize>,
 54    pub string: String,
 55}
 56
 57impl PartialEq for StringMatch {
 58    fn eq(&self, other: &Self) -> bool {
 59        self.cmp(other).is_eq()
 60    }
 61}
 62
 63impl Eq for StringMatch {}
 64
 65impl PartialOrd for StringMatch {
 66    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
 67        Some(self.cmp(other))
 68    }
 69}
 70
 71impl Ord for StringMatch {
 72    fn cmp(&self, other: &Self) -> Ordering {
 73        self.score
 74            .partial_cmp(&other.score)
 75            .unwrap_or(Ordering::Equal)
 76            .then_with(|| self.candidate_id.cmp(&other.candidate_id))
 77    }
 78}
 79
 80pub async fn match_strings(
 81    candidates: &[StringMatchCandidate],
 82    query: &str,
 83    smart_case: bool,
 84    max_results: usize,
 85    cancel_flag: &AtomicBool,
 86    executor: BackgroundExecutor,
 87) -> Vec<StringMatch> {
 88    if candidates.is_empty() || max_results == 0 {
 89        return Default::default();
 90    }
 91
 92    if query.is_empty() {
 93        return candidates
 94            .iter()
 95            .map(|candidate| StringMatch {
 96                candidate_id: candidate.id,
 97                score: 0.,
 98                positions: Default::default(),
 99                string: candidate.string.clone(),
100            })
101            .collect();
102    }
103
104    let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
105    let query = query.chars().collect::<Vec<_>>();
106
107    let lowercase_query = &lowercase_query;
108    let query = &query;
109    let query_char_bag = CharBag::from(&lowercase_query[..]);
110
111    let num_cpus = executor.num_cpus().min(candidates.len());
112    let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
113    let mut segment_results = (0..num_cpus)
114        .map(|_| Vec::with_capacity(max_results.min(candidates.len())))
115        .collect::<Vec<_>>();
116
117    executor
118        .scoped(|scope| {
119            for (segment_idx, results) in segment_results.iter_mut().enumerate() {
120                let cancel_flag = &cancel_flag;
121                scope.spawn(async move {
122                    let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
123                    let segment_end = cmp::min(segment_start + segment_size, candidates.len());
124                    let mut matcher = Matcher::new(
125                        query,
126                        lowercase_query,
127                        query_char_bag,
128                        smart_case,
129                        max_results,
130                    );
131
132                    matcher.match_candidates(
133                        &[],
134                        &[],
135                        candidates[segment_start..segment_end].iter(),
136                        results,
137                        cancel_flag,
138                        |candidate, score| StringMatch {
139                            candidate_id: candidate.id,
140                            score,
141                            positions: Vec::new(),
142                            string: candidate.string.to_string(),
143                        },
144                    );
145                });
146            }
147        })
148        .await;
149
150    let mut results = Vec::new();
151    for segment_result in segment_results {
152        if results.is_empty() {
153            results = segment_result;
154        } else {
155            util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
156        }
157    }
158    results
159}