Skip to main content

nucleo_picker/
match_list.rs

1//! # The list of match candidates
2//!
3//! ## Layout rules
4//! ### Layout rules for vertical alignment
5//!
6//! The core layout rules (in decreasing order of priority) are as follows.
7//!
8//! 1. Respect the padding below and above, except when the cursor is near 0.
9//! 2. Render as much of the selection as possible.
10//! 3. When the screen size increases, render new elements with lower index, and then elemenets
11//!    with higher index.
12//! 4. When the screen size decreases, delete whitespace, and then delete elements with higher
13//!    index, and then elements with lower index.
14//! 5. Change the location of the cursor on the screen as little as possible.
15//!
16//! ### Layout rules for horizontal alignment of items
17//!
18//! 1. Multi-line items must have the same amount of scroll for each line.
19//! 2. Do not hide highlighted characters.
20//! 3. Prefer to make the scroll as small as possible.
21//!
22//! ## Module organization
23//! This module contains the core [`MatchList`] struct. See the various update methods:
24//!
25//! 1. [`MatchList::resize`] for screen size changes.
26//! 2. [`MatchList::update_items`] for item list changes.
27//! 3. [`MatchList::selection_incr`] if the selection increases.
28//! 4. [`MatchList::selection_decr`] if the selection decreases.
29//! 5. [`MatchList::reset`] to set the cursor to 0 and completely redraw the screen.
30//! 6. [`MatchList::reparse`] to change the prompt string.
31//! 7. [`MatchList::update`] to wait for any changes in the match engine.
32//!
33//! Instead of merging the various methods together, we individually maintain methods for the
34//! various changes for performance so that only the layout computations required for the relevant
35//! changes are performed. For instance, on most frame renders, item updates are the most common.
36//!
37//! The actual implementations of the various layout methods are contained in the relevant
38//! sub-modules.
39#[cfg(test)]
40mod tests;
41
42mod draw;
43mod item;
44mod layout;
45mod span;
46mod unicode;
47
48use std::{
49    collections::{BTreeMap, btree_map::Entry},
50    num::NonZero,
51    ops::Range,
52    sync::Arc,
53};
54
55use self::{
56    layout::{reset, resize, selection, update},
57    unicode::Span,
58};
59use crate::{Injector, Render, incremental::Incremental};
60
61use nucleo::{
62    self as nc,
63    pattern::{CaseMatching as NucleoCaseMatching, Normalization as NucleoNormalization},
64};
65
66/// An event that modifies the selection in the match list.
67///
68/// # Multi-selection events
69///
70/// The following events are only handled by the picker in [multiple selection mode](crate::Picker#multiple-selections):
71/// - [`ToggleUp`](MatchListEvent::ToggleUp)
72/// - [`ToggleDown`](MatchListEvent::ToggleDown)
73/// - [`QueueAbove`](MatchListEvent::QueueAbove)
74/// - [`QueueBelow`](MatchListEvent::QueueBelow)
75/// - [`QueueMatches`](MatchListEvent::QueueMatches)
76/// - [`Unqueue`](MatchListEvent::Unqueue)
77/// - [`UnqueueAll`](MatchListEvent::UnqueueAll)
78///
79/// In this case, the corresponding movements are conditional: they will only be performed if the
80/// resulting (un)queue action is successful. This is relevant if the [selection count is
81/// bounded](crate::PickerOptions::max_selection_count) is bounded, since additional items cannot
82/// be added to the selection queue if the bound is reached, in which case the cursor will not
83/// move.
84#[derive(Debug, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum MatchListEvent {
87    /// Move the selection up `usize` items.
88    Up(usize),
89    /// Toggle the selection then move up `usize` items.
90    ToggleUp(usize),
91    /// Move the selection down `usize` items.
92    Down(usize),
93    /// Toggle the selection then move down `usize` items.
94    ToggleDown(usize),
95    /// Add the current item and `usize` items above to the item queue, moving the cursor to the
96    /// last selected item.
97    QueueAbove(usize),
98    /// Add the current item and `usize` items below to the item queue, moving the cursor to the
99    /// last selected item.
100    QueueBelow(usize),
101    /// Add all matching items to the item queue, preferring items with higher score.
102    QueueMatches,
103    /// Remove the current item from the item queue.
104    Unqueue,
105    /// Clear the item queue.
106    UnqueueAll,
107    /// Reset the selection to the start of the match list.
108    Reset,
109}
110
111/// A trait to describe items with a certain size.
112pub trait ItemSize {
113    /// The size of the item on the screen.
114    fn size(&self) -> usize;
115}
116
117/// A list of items with variable sizes.
118pub trait ItemList {
119    /// The item type of list.
120    type Item<'a>: ItemSize
121    where
122        Self: 'a;
123
124    /// The total number items in the list.
125    fn total(&self) -> u32;
126
127    /// An iterator over items below the cursor, iterating downwards.
128    fn lower(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
129
130    /// An iterator over items below and including the cursor, iterating downwards.
131    fn lower_inclusive(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
132
133    /// An iterator over items above cursor, iterating upwards.
134    fn higher(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
135
136    /// An iterator over items above and including the cursor, iterating upwards.
137    fn higher_inclusive(&self, selection: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
138}
139
140/// An automatic extension trait for an [`ItemList`].
141trait ItemListExt: ItemList {
142    /// Wrap the item sizes returned by [`lower`](ItemList::lower)
143    /// into a [`Incremental`].
144    fn sizes_lower<'a>(
145        &self,
146        cursor: u32,
147        vec: &'a mut Vec<usize>,
148    ) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
149        vec.clear();
150        Incremental::new(vec, self.lower(cursor).map(|item| item.size()))
151    }
152
153    /// Wrap the item sizes returned by [`lower_inclusive`](ItemList::lower_inclusive)
154    /// into a [`Incremental`].
155    fn sizes_lower_inclusive<'a>(
156        &self,
157        cursor: u32,
158        vec: &'a mut Vec<usize>,
159    ) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
160        vec.clear();
161        Incremental::new(vec, self.lower_inclusive(cursor).map(|item| item.size()))
162    }
163
164    /// Wrap the item sizes returned by [`higher`](ItemList::higher)
165    /// into an [`Incremental`].
166    fn sizes_higher<'a>(
167        &self,
168        cursor: u32,
169        vec: &'a mut Vec<usize>,
170    ) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
171        vec.clear();
172        Incremental::new(vec, self.higher(cursor).map(|item| item.size()))
173    }
174
175    /// Wrap the item sizes returned by [`higher_inclusive`](ItemList::higher)
176    /// into an [`Incremental`].
177    fn sizes_higher_inclusive<'a>(
178        &self,
179        cursor: u32,
180        vec: &'a mut Vec<usize>,
181    ) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
182        vec.clear();
183        Incremental::new(vec, self.higher_inclusive(cursor).map(|item| item.size()))
184    }
185}
186
187impl<B: ItemList> ItemListExt for B {}
188
189/// Context from the previous render used to update the screen correctly.
190#[derive(Debug)]
191struct MatchListState {
192    selection: u32,
193    below: u16,
194    above: u16,
195    size: u16,
196}
197
198/// Configuration used internally in the [`PickerState`].
199#[derive(Debug, Clone)]
200#[non_exhaustive]
201pub struct MatchListConfig {
202    /// Whether or not to do match highlighting.
203    pub highlight: bool,
204    /// Whether or not the screen is reversed.
205    pub reversed: bool,
206    /// The amount of padding around highlighted matches.
207    pub highlight_padding: u16,
208    /// The amount of padding when scrolling.
209    pub scroll_padding: u16,
210    /// Case matching behaviour for matches.
211    pub case_matching: NucleoCaseMatching,
212    /// Normalization behaviour for matches.
213    pub normalization: NucleoNormalization,
214}
215
216impl MatchListConfig {
217    pub const fn new() -> Self {
218        Self {
219            highlight: true,
220            reversed: false,
221            highlight_padding: 3,
222            scroll_padding: 3,
223            case_matching: NucleoCaseMatching::Smart,
224            normalization: NucleoNormalization::Smart,
225        }
226    }
227}
228
229impl Default for MatchListConfig {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235/// A buffer to hold match highlight information and cached line information in an underlying
236/// string slice.
237pub struct IndexBuffer {
238    /// Spans used to render items.
239    spans: Vec<Span>,
240    /// Sub-slices of `spans` corresponding to lines.
241    lines: Vec<Range<usize>>,
242    /// Indices generated from a match.
243    indices: Vec<u32>,
244}
245
246impl IndexBuffer {
247    /// Create a new buffer.
248    pub fn new() -> Self {
249        Self {
250            spans: Vec::with_capacity(16),
251            lines: Vec::with_capacity(4),
252            indices: Vec::with_capacity(16),
253        }
254    }
255}
256
257pub trait Queued {
258    type Output<'a, T: Send + Sync + 'static>;
259
260    fn is_empty(&self) -> bool;
261
262    fn clear(&mut self) -> bool;
263
264    fn deselect(&mut self, idx: u32) -> bool;
265
266    fn toggle(&mut self, idx: u32) -> bool;
267
268    /// Select a range of items.
269    ///
270    /// The index is the number of items consumed from the iterator.
271    ///
272    /// The boolean is whether or not any new items were queued.
273    fn select<I: IntoIterator<Item = u32>>(&mut self, items: I) -> (usize, bool);
274
275    fn is_queued(&self, idx: u32) -> bool;
276
277    fn count(&self, limit: Option<NonZero<u32>>) -> Option<(u32, Option<NonZero<u32>>)>;
278
279    fn init(limit: Option<NonZero<u32>>) -> Self;
280
281    fn into_only_selection<'a, T: Send + Sync + 'static>(
282        self,
283        snapshot: &'a nucleo::Snapshot<T>,
284        idx: u32,
285    ) -> Self::Output<'a, T>;
286
287    fn into_selection<'a, T: Send + Sync + 'static>(
288        self,
289        snapshot: &'a nucleo::Snapshot<T>,
290    ) -> Self::Output<'a, T>;
291}
292
293impl Queued for () {
294    type Output<'a, T: Send + Sync + 'static> = Option<&'a T>;
295
296    #[inline]
297    fn is_empty(&self) -> bool {
298        true
299    }
300
301    #[inline]
302    fn clear(&mut self) -> bool {
303        false
304    }
305
306    #[inline]
307    fn deselect(&mut self, _: u32) -> bool {
308        false
309    }
310
311    #[inline]
312    fn toggle(&mut self, _: u32) -> bool {
313        false
314    }
315
316    #[inline]
317    fn select<I: IntoIterator<Item = u32>>(&mut self, _: I) -> (usize, bool) {
318        (0, false)
319    }
320
321    #[inline]
322    fn is_queued(&self, _: u32) -> bool {
323        false
324    }
325
326    #[inline]
327    fn init(_: Option<NonZero<u32>>) -> Self {}
328
329    #[inline]
330    fn into_selection<'a, T: Send + Sync + 'static>(
331        self,
332        _: &'a nucleo::Snapshot<T>,
333    ) -> Self::Output<'a, T> {
334        None
335    }
336
337    #[inline]
338    fn into_only_selection<'a, T: Send + Sync + 'static>(
339        self,
340        snapshot: &'a nucleo::Snapshot<T>,
341        idx: u32,
342    ) -> Self::Output<'a, T> {
343        Some(snapshot.get_item(idx).unwrap().data)
344    }
345
346    #[inline]
347    fn count(&self, _: Option<NonZero<u32>>) -> Option<(u32, Option<NonZero<u32>>)> {
348        None
349    }
350}
351
352impl Queued for SelectedIndices {
353    type Output<'a, T: Send + Sync + 'static> = Selection<'a, T>;
354
355    #[inline]
356    fn is_empty(&self) -> bool {
357        self.inner.is_empty()
358    }
359
360    #[inline]
361    fn clear(&mut self) -> bool {
362        if self.is_empty() {
363            false
364        } else {
365            self.inner.clear();
366            true
367        }
368    }
369
370    #[inline]
371    fn toggle(&mut self, idx: u32) -> bool {
372        let n = self.inner.len();
373        match self.inner.entry(idx) {
374            Entry::Occupied(occupied_entry) => {
375                occupied_entry.remove_entry();
376                true
377            }
378            Entry::Vacant(vacant_entry) => {
379                if self.limit.is_none_or(|l| n < l.get() as usize) {
380                    vacant_entry.insert(Self::next_order(&mut self.next_order));
381                    true
382                } else {
383                    false
384                }
385            }
386        }
387    }
388
389    #[inline]
390    fn deselect(&mut self, idx: u32) -> bool {
391        self.inner.remove(&idx).is_some()
392    }
393
394    fn select<I: IntoIterator<Item = u32>>(&mut self, items: I) -> (usize, bool) {
395        let mut toggled = false;
396        let mut consumed: usize = 0;
397
398        for it in items {
399            let current_len = self.inner.len();
400            match self.inner.entry(it) {
401                Entry::Vacant(vacant_entry)
402                    if self.limit.is_none_or(|l| current_len < l.get() as usize) =>
403                {
404                    toggled = true;
405                    consumed += 1;
406                    vacant_entry.insert(Self::next_order(&mut self.next_order));
407                }
408                Entry::Vacant(_) => break,
409                Entry::Occupied(_) => {
410                    consumed += 1;
411                }
412            }
413        }
414
415        (consumed, toggled)
416    }
417
418    #[inline]
419    fn is_queued(&self, idx: u32) -> bool {
420        self.inner.contains_key(&idx)
421    }
422
423    #[inline]
424    fn init(limit: Option<NonZero<u32>>) -> Self {
425        Self {
426            inner: BTreeMap::new(),
427            next_order: 0,
428            limit,
429        }
430    }
431
432    #[inline]
433    fn into_selection<'a, T: Send + Sync + 'static>(
434        self,
435        snapshot: &'a nucleo::Snapshot<T>,
436    ) -> Self::Output<'a, T> {
437        Self::Output {
438            snapshot,
439            queued: self,
440        }
441    }
442
443    #[inline]
444    fn into_only_selection<'a, T: Send + Sync + 'static>(
445        mut self,
446        snapshot: &'a nucleo::Snapshot<T>,
447        idx: u32,
448    ) -> Self::Output<'a, T> {
449        self.insert(idx);
450        Self::Output {
451            snapshot,
452            queued: self,
453        }
454    }
455
456    #[inline]
457    fn count(&self, limit: Option<NonZero<u32>>) -> Option<(u32, Option<NonZero<u32>>)> {
458        Some((self.inner.len() as u32, limit))
459    }
460}
461
462pub struct SelectedIndices {
463    inner: BTreeMap<u32, u64>,
464    next_order: u64,
465    limit: Option<NonZero<u32>>,
466}
467
468impl SelectedIndices {
469    fn insert(&mut self, idx: u32) {
470        if let Entry::Vacant(entry) = self.inner.entry(idx) {
471            entry.insert(Self::next_order(&mut self.next_order));
472        }
473    }
474
475    fn next_order(next_order: &mut u64) -> u64 {
476        let order = *next_order;
477        *next_order += 1;
478        order
479    }
480}
481
482/// The selected items when the picker quits.
483///
484/// This is the return type of the various `pick_multi*` methods of a [`Picker`](crate::Picker).
485/// Iterate over the picked items with [`iter`](Self::iter). Also see the documentation for
486/// [multiple selections](crate::Picker#multiple-selections)
487///
488/// The lifetime of this struct is bound to the lifetime of the picker from which it originated.
489pub struct Selection<'a, T: Send + Sync + 'static> {
490    snapshot: &'a nc::Snapshot<T>,
491    queued: SelectedIndices,
492}
493
494impl<'a, T: Send + Sync + 'static> Selection<'a, T> {
495    /// Returns an iterator over the other selected items.
496    ///
497    /// The iterator contains each selected item exactly once, sorted by index based on the order
498    /// in which the picker received the items. If multiple threads populate the picker, the
499    /// relative order between different threads is unspecified. Note that items are deduplicated
500    /// based on the selection index instead of using any properties of the type `T` itself.
501    ///
502    /// See [`iter_selected_order`](Self::iter_selected_order) to obtain the items in the order
503    /// selected by the user.
504    ///
505    /// The iterator will be empty if the picker quit without selecting any items.
506    pub fn iter(&self) -> impl ExactSizeIterator<Item = &'a T> + DoubleEndedIterator {
507        self.queued.inner.keys().map(|idx| {
508            // SAFETY: the indices were produced by the same snapshot which is stored inside this
509            // struct, and the lifetime prevents the indices from being invalidated until this struct
510            // is dropped
511            unsafe { self.snapshot.get_item_unchecked(*idx).data }
512        })
513    }
514
515    /// Returns an iterator over the selected items, sorted by selection order.
516    ///
517    /// The iterator contains each selected item exactly once. If an item is un-selected and
518    /// selected again, the order is determined by the final selection.
519    ///
520    /// Note that the current implementation does not internally store the selected items by
521    /// selection order, so calling this method requires allocating a new container and then sorting.
522    ///
523    /// The iterator will be empty if the picker quit without selecting any items.
524    pub fn iter_selected_order(
525        &self,
526    ) -> impl ExactSizeIterator<Item = &'a T> + DoubleEndedIterator {
527        let snapshot = self.snapshot;
528        let mut selected_items = self
529            .queued
530            .inner
531            .iter()
532            .map(|(&idx, &order)| (order, idx))
533            .collect::<Vec<_>>();
534        selected_items.sort_unstable_by_key(|&(order, _)| order);
535
536        selected_items.into_iter().map(move |(_, idx)| {
537            // SAFETY: the indices were produced by the same snapshot which is stored inside this
538            // struct, and the lifetime prevents the indices from being invalidated until this struct
539            // is dropped
540            unsafe { snapshot.get_item_unchecked(idx).data }
541        })
542    }
543
544    /// Returns if there were no selected items.
545    pub fn is_empty(&self) -> bool {
546        self.queued.inner.is_empty()
547    }
548
549    /// Returns the number of selected items.
550    pub fn len(&self) -> usize {
551        self.queued.inner.len()
552    }
553}
554
555/// A component for representing the list of successful matches.
556///
557/// This component has two main parts: the internal [`nucleo::Nucleo`] match engine, as well as a
558/// stateful representation of the match items which are currently on the screen. See the module
559/// level documentation for more detail.
560pub struct MatchList<T: Send + Sync + 'static, R> {
561    /// The current selection; this corresponds to a valid index if and only if the current
562    /// snapshot has more than one element.
563    selection: u32,
564    /// The size of the screen last time the screen changed.
565    size: u16,
566    /// The layout buffer below and including the matched item.
567    below: Vec<usize>,
568    /// The layout buffer above the matched item.
569    above: Vec<usize>,
570    /// Configuration for drawing.
571    config: MatchListConfig,
572    /// The internal matcher engine.
573    nucleo: nc::Nucleo<T>,
574    /// Scratch space for index computations during rendering.
575    scratch: IndexBuffer,
576    /// The method which actually renders the items.
577    render: Arc<R>,
578    /// The internal matcher.
579    matcher: nc::Matcher,
580    /// A cache of the prompt, used to decide if the prompt has changed.
581    prompt: String,
582}
583
584impl<T: Send + Sync + 'static, R> MatchList<T, R> {
585    /// Initialize a new [`MatchList`] with the provided configuration and initial state.
586    pub fn new(
587        config: MatchListConfig,
588        nucleo_config: nc::Config,
589        nucleo: nc::Nucleo<T>,
590        render: Arc<R>,
591    ) -> Self {
592        Self {
593            size: 0,
594            selection: 0,
595            // queued_items: HashMap::with_hasher(BuildHasherDefault::new()),
596            below: Vec::with_capacity(128),
597            above: Vec::with_capacity(128),
598            config,
599            nucleo,
600            matcher: nc::Matcher::new(nucleo_config),
601            render,
602            scratch: IndexBuffer::new(),
603            prompt: String::with_capacity(32),
604        }
605    }
606
607    pub fn reversed(&self) -> bool {
608        self.config.reversed
609    }
610
611    /// A convenience function to render a given item using the internal [`Render`] implementation.
612    pub fn render<'a>(&self, item: &'a T) -> <R as Render<T>>::Str<'a>
613    where
614        R: Render<T>,
615    {
616        self.render.render(item)
617    }
618
619    /// Replace the renderer with a new instance, immediately restarting the matcher engine.
620    pub fn reset_renderer(&mut self, render: R) {
621        self.restart();
622        self.render = render.into();
623    }
624
625    /// Get an [`Injector`] to add new match elements.
626    pub fn injector(&self) -> Injector<T, R> {
627        Injector::new(self.nucleo.injector(), self.render.clone())
628    }
629
630    /// Clear all of the items and restart the match engine.
631    pub fn restart(&mut self) {
632        self.nucleo.restart(true);
633        self.update_items();
634    }
635
636    /// Replace the internal [`nucleo`] configuration.
637    pub fn update_nucleo_config(&mut self, config: nc::Config) {
638        self.nucleo.update_config(config);
639    }
640
641    /// Returns a self-contained representation of the screen state required for correct layout
642    /// update computations.
643    fn state(&self) -> MatchListState {
644        let below = self.below.iter().sum::<usize>() as u16;
645        let above = self.above.iter().sum::<usize>() as u16;
646        MatchListState {
647            selection: self.selection,
648            below: self.size - above,
649            above: self.size - below,
650            size: self.size,
651        }
652    }
653
654    /// The total amount of whitespace present in the displayed match list.
655    fn whitespace(&self) -> u16 {
656        self.size
657            - self.below.iter().sum::<usize>() as u16
658            - self.above.iter().sum::<usize>() as u16
659    }
660
661    /// The amount of padding corresponding to the provided size.
662    pub fn padding(&self, size: u16) -> u16 {
663        self.config.scroll_padding.min(size.saturating_sub(1) / 2)
664    }
665
666    /// Replace the prompt string with an updated value.
667    pub fn reparse(&mut self, new: &str) {
668        // appending if the new value has the previous value as a prefix and also does not end in a
669        // trailing unescaped '\\'
670        let appending = match new.strip_prefix(&self.prompt) {
671            Some(rest) => {
672                if rest.is_empty() {
673                    // the strings are the same so we don't need to do anything
674                    return;
675                } else {
676                    true
677                }
678            }
679            None => false,
680        };
681        self.nucleo.pattern.reparse(
682            0,
683            new,
684            self.config.case_matching,
685            self.config.normalization,
686            appending,
687        );
688        self.prompt = new.to_owned();
689    }
690
691    /// Whether or not the list of items is empty.
692    pub fn is_empty(&self) -> bool {
693        self.nucleo.snapshot().matched_item_count() == 0
694    }
695
696    pub fn selection(&self) -> u32 {
697        self.selection
698    }
699
700    pub fn max_selection(&self) -> u32 {
701        self.nucleo
702            .snapshot()
703            .matched_item_count()
704            .saturating_sub(1)
705    }
706
707    fn idx_from_match_unchecked(&self, n: u32) -> u32 {
708        self.nucleo
709            .snapshot()
710            .matches()
711            .get(n as usize)
712            .unwrap()
713            .idx
714    }
715
716    pub fn unqueue_item<Q: Queued>(&mut self, queued_items: &mut Q, n: u32) -> bool {
717        queued_items.deselect(self.idx_from_match_unchecked(n))
718    }
719
720    pub fn queue_items_above<Q: Queued>(
721        &mut self,
722        queued_items: &mut Q,
723        n: u32,
724        ct: usize,
725    ) -> (usize, bool) {
726        let matches = self.nucleo.snapshot().matches();
727        let start = n as usize;
728        let end = (start + ct).min(matches.len() - 1);
729        queued_items.select(matches[start..=end].iter().map(|m| m.idx))
730    }
731
732    pub fn queue_items_below<Q: Queued>(
733        &mut self,
734        queued_items: &mut Q,
735        n: u32,
736        ct: usize,
737    ) -> (usize, bool) {
738        let matches = self.nucleo.snapshot().matches();
739        let start = n as usize;
740        let end = start.saturating_sub(ct);
741        queued_items.select(matches[end..=start].iter().rev().map(|m| m.idx))
742    }
743
744    pub fn queue_all<Q: Queued>(&mut self, queued_items: &mut Q) -> bool {
745        queued_items
746            .select(self.nucleo.snapshot().matches().iter().map(|m| m.idx))
747            .1
748    }
749
750    pub fn toggle_queued_item<Q: Queued>(&mut self, queued_items: &mut Q, n: u32) -> bool {
751        queued_items.toggle(self.idx_from_match_unchecked(n))
752    }
753
754    pub fn select_none<Q: Queued>(&self, mut queued_items: Q) -> Q::Output<'_, T> {
755        queued_items.clear();
756        self.select_queued(queued_items)
757    }
758
759    pub fn select_one<Q: Queued>(&self, queued_items: Q, n: u32) -> Q::Output<'_, T> {
760        let idx = self.idx_from_match_unchecked(n);
761        let snapshot = self.nucleo.snapshot();
762        queued_items.into_only_selection(snapshot, idx)
763    }
764
765    pub fn select_queued<Q: Queued>(&self, queued_items: Q) -> Q::Output<'_, T> {
766        let snapshot = self.nucleo.snapshot();
767        queued_items.into_selection(snapshot)
768    }
769
770    /// Return the range corresponding to the matched items visible on the screen.
771    pub fn selection_range(&self) -> std::ops::RangeInclusive<usize> {
772        if self.config.reversed {
773            self.selection as usize - self.above.len()
774                ..=self.selection as usize + self.below.len() - 1
775        } else {
776            self.selection as usize + 1 - self.below.len()
777                ..=self.selection as usize + self.above.len()
778        }
779    }
780
781    /// Recompute the match layout when the screen size has changed.
782    pub fn resize(&mut self, total_size: u16) {
783        // check for zero, so the 'clamp' call dows not fail
784        if total_size == 0 {
785            self.size = 0;
786            self.above.clear();
787            self.below.clear();
788            return;
789        }
790
791        let buffer = self.nucleo.snapshot();
792
793        // check for no elements, so the `sizes_below` and `sizes_above` calls do not fail
794        if buffer.total() == 0 {
795            self.size = total_size;
796            return;
797        }
798
799        let padding = self.padding(total_size);
800
801        let mut previous = self.state();
802
803        if self.config.reversed {
804            // since the padding could change, make sure the value of 'below' is valid for the new
805            // padding values
806            previous.below = previous.below.clamp(padding, total_size - padding - 1);
807
808            let sizes_below_incl = buffer.sizes_higher_inclusive(self.selection, &mut self.below);
809            let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
810
811            if self.size <= total_size {
812                resize::larger_rev(previous, total_size, padding, sizes_below_incl, sizes_above);
813            } else {
814                resize::smaller_rev(
815                    previous,
816                    total_size,
817                    padding,
818                    padding,
819                    sizes_below_incl,
820                    sizes_above,
821                );
822            }
823        } else {
824            // since the padding could change, make sure the value of 'above' is valid for the new
825            // padding values
826            previous.above = previous.above.clamp(padding, total_size - padding - 1);
827
828            let sizes_below_incl = buffer.sizes_lower_inclusive(self.selection, &mut self.below);
829            let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
830
831            if self.size <= total_size {
832                resize::larger(previous, total_size, sizes_below_incl, sizes_above);
833            } else {
834                resize::smaller(previous, total_size, padding, sizes_below_incl, sizes_above);
835            }
836        }
837
838        self.size = total_size;
839    }
840
841    /// Check if the internal match workers have returned any new updates for matched items.
842    pub fn update(&mut self, millis: u64) -> bool {
843        let status = self.nucleo.tick(millis);
844        if status.changed {
845            self.update_items();
846        }
847        status.changed
848    }
849
850    /// Reset the layout, setting the cursor to '0' and rendering the items.
851    pub fn reset(&mut self) -> bool {
852        let buffer = self.nucleo.snapshot();
853        let padding = self.padding(self.size);
854        if self.selection != 0 {
855            if self.config.reversed {
856                let sizes_below_incl = buffer.sizes_higher_inclusive(0, &mut self.below);
857                self.above.clear();
858
859                reset::reset_rev(self.size, sizes_below_incl);
860            } else {
861                let sizes_below_incl = buffer.sizes_lower_inclusive(0, &mut self.below);
862                let sizes_above = buffer.sizes_higher(0, &mut self.above);
863
864                reset::reset(self.size, padding, sizes_below_incl, sizes_above);
865            }
866
867            self.selection = 0;
868            true
869        } else {
870            false
871        }
872    }
873
874    /// Update the layout with the modified item list.
875    pub fn update_items(&mut self) {
876        let buffer = self.nucleo.snapshot();
877        // clamp the previous cursor in case it has become invalid for the updated items
878        self.selection = self.selection.min(buffer.total().saturating_sub(1));
879        let previous = self.state();
880        let padding = self.padding(self.size);
881
882        if buffer.total() > 0 {
883            if self.config.reversed {
884                let sizes_below_incl =
885                    buffer.sizes_higher_inclusive(self.selection, &mut self.below);
886                let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
887
888                update::items_rev(previous, padding, sizes_below_incl, sizes_above);
889            } else {
890                let sizes_below_incl =
891                    buffer.sizes_lower_inclusive(self.selection, &mut self.below);
892                let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
893
894                update::items(previous, padding, sizes_below_incl, sizes_above);
895            }
896        } else {
897            self.below.clear();
898            self.above.clear();
899            self.selection = 0;
900        }
901    }
902
903    #[inline]
904    pub fn set_selection(&mut self, new_selection: u32) -> bool {
905        let buffer = self.nucleo.snapshot();
906        let new_selection = new_selection.min(buffer.total().saturating_sub(1));
907
908        let previous = self.state();
909        let padding = self.padding(self.size);
910
911        if new_selection == 0 {
912            self.reset()
913        } else if new_selection > self.selection {
914            if self.config.reversed {
915                let sizes_below_incl =
916                    buffer.sizes_higher_inclusive(new_selection, &mut self.below);
917                let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
918
919                selection::incr_rev(
920                    previous,
921                    new_selection,
922                    padding,
923                    padding,
924                    sizes_below_incl,
925                    sizes_above,
926                );
927            } else {
928                let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
929                let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
930
931                selection::incr(
932                    previous,
933                    new_selection,
934                    padding,
935                    sizes_below_incl,
936                    sizes_above,
937                );
938            }
939
940            self.selection = new_selection;
941
942            true
943        } else if new_selection < self.selection {
944            if self.config.reversed {
945                let sizes_below_incl =
946                    buffer.sizes_higher_inclusive(new_selection, &mut self.below);
947                let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
948
949                selection::decr_rev(
950                    previous,
951                    new_selection,
952                    padding,
953                    sizes_below_incl,
954                    sizes_above,
955                );
956            } else {
957                let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
958                let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
959
960                selection::decr(
961                    previous,
962                    new_selection,
963                    padding,
964                    padding,
965                    sizes_below_incl,
966                    sizes_above,
967                );
968            }
969
970            self.selection = new_selection;
971
972            true
973        } else {
974            false
975        }
976    }
977
978    /// Increment the selection by the given amount.
979    #[cfg(test)]
980    pub fn selection_incr(&mut self, increase: u32) -> bool {
981        let new_selection = self
982            .selection
983            .saturating_add(increase)
984            .min(self.nucleo.snapshot().total().saturating_sub(1));
985
986        self.set_selection(new_selection)
987    }
988
989    /// Decrement the selection by the given amount.
990    #[cfg(test)]
991    pub fn selection_decr(&mut self, decrease: u32) -> bool {
992        let new_selection = self.selection.saturating_sub(decrease);
993
994        self.set_selection(new_selection)
995    }
996}