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() as u32;
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()) {
380                    vacant_entry.insert(());
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        match self.limit {
399            None => {
400                for it in items {
401                    match self.inner.entry(it) {
402                        Entry::Vacant(vacant_entry) => {
403                            toggled = true;
404                            consumed += 1;
405                            vacant_entry.insert(());
406                        }
407                        Entry::Occupied(_) => {
408                            consumed += 1;
409                        }
410                    }
411                }
412            }
413            Some(l) => {
414                for it in items {
415                    let current_len = self.inner.len();
416                    match self.inner.entry(it) {
417                        Entry::Vacant(vacant_entry) => {
418                            if current_len < l.get() as usize {
419                                toggled = true;
420                                consumed += 1;
421                                vacant_entry.insert(());
422                            } else {
423                                break;
424                            }
425                        }
426                        Entry::Occupied(_) => {
427                            consumed += 1;
428                        }
429                    }
430                }
431            }
432        }
433        (consumed, toggled)
434    }
435
436    #[inline]
437    fn is_queued(&self, idx: u32) -> bool {
438        self.inner.contains_key(&idx)
439    }
440
441    #[inline]
442    fn init(limit: Option<NonZero<u32>>) -> Self {
443        Self {
444            inner: BTreeMap::new(),
445            limit,
446        }
447    }
448
449    #[inline]
450    fn into_selection<'a, T: Send + Sync + 'static>(
451        self,
452        snapshot: &'a nucleo::Snapshot<T>,
453    ) -> Self::Output<'a, T> {
454        Self::Output {
455            snapshot,
456            queued: self,
457        }
458    }
459
460    #[inline]
461    fn into_only_selection<'a, T: Send + Sync + 'static>(
462        mut self,
463        snapshot: &'a nucleo::Snapshot<T>,
464        idx: u32,
465    ) -> Self::Output<'a, T> {
466        self.inner.insert(idx, ());
467        Self::Output {
468            snapshot,
469            queued: self,
470        }
471    }
472
473    #[inline]
474    fn count(&self, limit: Option<NonZero<u32>>) -> Option<(u32, Option<NonZero<u32>>)> {
475        Some((self.inner.len() as u32, limit))
476    }
477}
478
479pub struct SelectedIndices {
480    // FIXME: replace with BTreeSet when the entry API lands
481    // > https://github.com/rust-lang/rust/issues/133549)
482    inner: BTreeMap<u32, ()>,
483    limit: Option<NonZero<u32>>,
484}
485
486/// The selected items when the picker quits.
487///
488/// This is the return type of the various `pick_multi*` methods of a [`Picker`](crate::Picker).
489/// Iterate over the picked items with [`iter`](Self::iter). If no items were selected, ththe struct
490/// will be [empty](Self::is_empty). Also see the docs on
491/// [multiple selections](crate::Picker#multiple-selections)
492///
493/// The lifetime of this struct is bound to the lifetime of the picker from which it originated.
494pub struct Selection<'a, T: Send + Sync + 'static> {
495    snapshot: &'a nc::Snapshot<T>,
496    // FIXME: replace with BTreeSet when the entry API lands
497    // > https://github.com/rust-lang/rust/issues/133549)
498    queued: SelectedIndices,
499}
500
501impl<'a, T: Send + Sync + 'static> Selection<'a, T> {
502    /// Returns an iterator over the other selected items.
503    ///
504    ///
505    /// The iterator contains each selected item exactly once, sorted by index based on the order
506    /// in which the picker received the items. Note that items are deduplicated based on the
507    /// selection index instead of using any properties of the type `T` itself.
508    ///
509    /// The iterator will be empty if the picker quit without selecting any items.
510    pub fn iter(&self) -> impl ExactSizeIterator<Item = &'a T> + DoubleEndedIterator {
511        self.queued.inner.keys().map(|idx| {
512            // SAFETY: the indices were produced by the same snapshot which is stored inside this
513            // struct, and the lifetime prevents the indices from being invalidated until this struct
514            // is dropped
515            unsafe { self.snapshot.get_item_unchecked(*idx).data }
516        })
517    }
518
519    /// Returns if there were no selected items.
520    pub fn is_empty(&self) -> bool {
521        self.queued.inner.is_empty()
522    }
523
524    /// Returns the number of selected items.
525    pub fn len(&self) -> usize {
526        self.queued.inner.len()
527    }
528}
529
530/// A component for representing the list of successful matches.
531///
532/// This component has two main parts: the internal [`nucleo::Nucleo`] match engine, as well as a
533/// stateful representation of the match items which are currently on the screen. See the module
534/// level documentation for more detail.
535pub struct MatchList<T: Send + Sync + 'static, R> {
536    /// The current selection; this corresponds to a valid index if and only if the current
537    /// snapshot has more than one element.
538    selection: u32,
539    /// The size of the screen last time the screen changed.
540    size: u16,
541    /// The layout buffer below and including the matched item.
542    below: Vec<usize>,
543    /// The layout buffer above the matched item.
544    above: Vec<usize>,
545    /// Configuration for drawing.
546    config: MatchListConfig,
547    /// The internal matcher engine.
548    nucleo: nc::Nucleo<T>,
549    /// Scratch space for index computations during rendering.
550    scratch: IndexBuffer,
551    /// The method which actually renders the items.
552    render: Arc<R>,
553    /// The internal matcher.
554    matcher: nc::Matcher,
555    /// A cache of the prompt, used to decide if the prompt has changed.
556    prompt: String,
557}
558
559impl<T: Send + Sync + 'static, R> MatchList<T, R> {
560    /// Initialize a new [`MatchList`] with the provided configuration and initial state.
561    pub fn new(
562        config: MatchListConfig,
563        nucleo_config: nc::Config,
564        nucleo: nc::Nucleo<T>,
565        render: Arc<R>,
566    ) -> Self {
567        Self {
568            size: 0,
569            selection: 0,
570            // queued_items: HashMap::with_hasher(BuildHasherDefault::new()),
571            below: Vec::with_capacity(128),
572            above: Vec::with_capacity(128),
573            config,
574            nucleo,
575            matcher: nc::Matcher::new(nucleo_config),
576            render,
577            scratch: IndexBuffer::new(),
578            prompt: String::with_capacity(32),
579        }
580    }
581
582    pub fn reversed(&self) -> bool {
583        self.config.reversed
584    }
585
586    /// A convenience function to render a given item using the internal [`Render`] implementation.
587    pub fn render<'a>(&self, item: &'a T) -> <R as Render<T>>::Str<'a>
588    where
589        R: Render<T>,
590    {
591        self.render.render(item)
592    }
593
594    /// Replace the renderer with a new instance, immediately restarting the matcher engine.
595    pub fn reset_renderer(&mut self, render: R) {
596        self.restart();
597        self.render = render.into();
598    }
599
600    /// Get an [`Injector`] to add new match elements.
601    pub fn injector(&self) -> Injector<T, R> {
602        Injector::new(self.nucleo.injector(), self.render.clone())
603    }
604
605    /// Clear all of the items and restart the match engine.
606    pub fn restart(&mut self) {
607        self.nucleo.restart(true);
608        self.update_items();
609    }
610
611    /// Replace the internal [`nucleo`] configuration.
612    pub fn update_nucleo_config(&mut self, config: nc::Config) {
613        self.nucleo.update_config(config);
614    }
615
616    /// Returns a self-contained representation of the screen state required for correct layout
617    /// update computations.
618    fn state(&self) -> MatchListState {
619        let below = self.below.iter().sum::<usize>() as u16;
620        let above = self.above.iter().sum::<usize>() as u16;
621        MatchListState {
622            selection: self.selection,
623            below: self.size - above,
624            above: self.size - below,
625            size: self.size,
626        }
627    }
628
629    /// The total amount of whitespace present in the displayed match list.
630    fn whitespace(&self) -> u16 {
631        self.size
632            - self.below.iter().sum::<usize>() as u16
633            - self.above.iter().sum::<usize>() as u16
634    }
635
636    /// The amount of padding corresponding to the provided size.
637    pub fn padding(&self, size: u16) -> u16 {
638        self.config.scroll_padding.min(size.saturating_sub(1) / 2)
639    }
640
641    /// Replace the prompt string with an updated value.
642    pub fn reparse(&mut self, new: &str) {
643        // appending if the new value has the previous value as a prefix and also does not end in a
644        // trailing unescaped '\\'
645        let appending = match new.strip_prefix(&self.prompt) {
646            Some(rest) => {
647                if rest.is_empty() {
648                    // the strings are the same so we don't need to do anything
649                    return;
650                } else {
651                    true
652                }
653            }
654            None => false,
655        };
656        self.nucleo.pattern.reparse(
657            0,
658            new,
659            self.config.case_matching,
660            self.config.normalization,
661            appending,
662        );
663        self.prompt = new.to_owned();
664    }
665
666    /// Whether or not the list of items is empty.
667    pub fn is_empty(&self) -> bool {
668        self.nucleo.snapshot().matched_item_count() == 0
669    }
670
671    pub fn selection(&self) -> u32 {
672        self.selection
673    }
674
675    pub fn max_selection(&self) -> u32 {
676        self.nucleo
677            .snapshot()
678            .matched_item_count()
679            .saturating_sub(1)
680    }
681
682    fn idx_from_match_unchecked(&self, n: u32) -> u32 {
683        self.nucleo
684            .snapshot()
685            .matches()
686            .get(n as usize)
687            .unwrap()
688            .idx
689    }
690
691    pub fn unqueue_item<Q: Queued>(&mut self, queued_items: &mut Q, n: u32) -> bool {
692        queued_items.deselect(self.idx_from_match_unchecked(n))
693    }
694
695    pub fn queue_items_above<Q: Queued>(
696        &mut self,
697        queued_items: &mut Q,
698        n: u32,
699        ct: usize,
700    ) -> (usize, bool) {
701        let matches = self.nucleo.snapshot().matches();
702        let start = n as usize;
703        let end = (start + ct).min(matches.len() - 1);
704        queued_items.select(matches[start..=end].iter().map(|m| m.idx))
705    }
706
707    pub fn queue_items_below<Q: Queued>(
708        &mut self,
709        queued_items: &mut Q,
710        n: u32,
711        ct: usize,
712    ) -> (usize, bool) {
713        let matches = self.nucleo.snapshot().matches();
714        let start = n as usize;
715        let end = start.saturating_sub(ct);
716        queued_items.select(matches[end..=start].iter().rev().map(|m| m.idx))
717    }
718
719    pub fn queue_all<Q: Queued>(&mut self, queued_items: &mut Q) -> bool {
720        queued_items
721            .select(self.nucleo.snapshot().matches().iter().map(|m| m.idx))
722            .1
723    }
724
725    pub fn toggle_queued_item<Q: Queued>(&mut self, queued_items: &mut Q, n: u32) -> bool {
726        queued_items.toggle(self.idx_from_match_unchecked(n))
727    }
728
729    pub fn select_none<Q: Queued>(&self, mut queued_items: Q) -> Q::Output<'_, T> {
730        queued_items.clear();
731        self.select_queued(queued_items)
732    }
733
734    pub fn select_one<Q: Queued>(&self, queued_items: Q, n: u32) -> Q::Output<'_, T> {
735        let idx = self.idx_from_match_unchecked(n);
736        let snapshot = self.nucleo.snapshot();
737        queued_items.into_only_selection(snapshot, idx)
738    }
739
740    pub fn select_queued<Q: Queued>(&self, queued_items: Q) -> Q::Output<'_, T> {
741        let snapshot = self.nucleo.snapshot();
742        queued_items.into_selection(snapshot)
743    }
744
745    /// Return the range corresponding to the matched items visible on the screen.
746    pub fn selection_range(&self) -> std::ops::RangeInclusive<usize> {
747        if self.config.reversed {
748            self.selection as usize - self.above.len()
749                ..=self.selection as usize + self.below.len() - 1
750        } else {
751            self.selection as usize + 1 - self.below.len()
752                ..=self.selection as usize + self.above.len()
753        }
754    }
755
756    /// Recompute the match layout when the screen size has changed.
757    pub fn resize(&mut self, total_size: u16) {
758        // check for zero, so the 'clamp' call dows not fail
759        if total_size == 0 {
760            self.size = 0;
761            self.above.clear();
762            self.below.clear();
763            return;
764        }
765
766        let buffer = self.nucleo.snapshot();
767
768        // check for no elements, so the `sizes_below` and `sizes_above` calls do not fail
769        if buffer.total() == 0 {
770            self.size = total_size;
771            return;
772        }
773
774        let padding = self.padding(total_size);
775
776        let mut previous = self.state();
777
778        if self.config.reversed {
779            // since the padding could change, make sure the value of 'below' is valid for the new
780            // padding values
781            previous.below = previous.below.clamp(padding, total_size - padding - 1);
782
783            let sizes_below_incl = buffer.sizes_higher_inclusive(self.selection, &mut self.below);
784            let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
785
786            if self.size <= total_size {
787                resize::larger_rev(previous, total_size, padding, sizes_below_incl, sizes_above);
788            } else {
789                resize::smaller_rev(
790                    previous,
791                    total_size,
792                    padding,
793                    padding,
794                    sizes_below_incl,
795                    sizes_above,
796                );
797            }
798        } else {
799            // since the padding could change, make sure the value of 'above' is valid for the new
800            // padding values
801            previous.above = previous.above.clamp(padding, total_size - padding - 1);
802
803            let sizes_below_incl = buffer.sizes_lower_inclusive(self.selection, &mut self.below);
804            let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
805
806            if self.size <= total_size {
807                resize::larger(previous, total_size, sizes_below_incl, sizes_above);
808            } else {
809                resize::smaller(previous, total_size, padding, sizes_below_incl, sizes_above);
810            }
811        }
812
813        self.size = total_size;
814    }
815
816    /// Check if the internal match workers have returned any new updates for matched items.
817    pub fn update(&mut self, millis: u64) -> bool {
818        let status = self.nucleo.tick(millis);
819        if status.changed {
820            self.update_items();
821        }
822        status.changed
823    }
824
825    /// Reset the layout, setting the cursor to '0' and rendering the items.
826    pub fn reset(&mut self) -> bool {
827        let buffer = self.nucleo.snapshot();
828        let padding = self.padding(self.size);
829        if self.selection != 0 {
830            if self.config.reversed {
831                let sizes_below_incl = buffer.sizes_higher_inclusive(0, &mut self.below);
832                self.above.clear();
833
834                reset::reset_rev(self.size, sizes_below_incl);
835            } else {
836                let sizes_below_incl = buffer.sizes_lower_inclusive(0, &mut self.below);
837                let sizes_above = buffer.sizes_higher(0, &mut self.above);
838
839                reset::reset(self.size, padding, sizes_below_incl, sizes_above);
840            }
841
842            self.selection = 0;
843            true
844        } else {
845            false
846        }
847    }
848
849    /// Update the layout with the modified item list.
850    pub fn update_items(&mut self) {
851        let buffer = self.nucleo.snapshot();
852        // clamp the previous cursor in case it has become invalid for the updated items
853        self.selection = self.selection.min(buffer.total().saturating_sub(1));
854        let previous = self.state();
855        let padding = self.padding(self.size);
856
857        if buffer.total() > 0 {
858            if self.config.reversed {
859                let sizes_below_incl =
860                    buffer.sizes_higher_inclusive(self.selection, &mut self.below);
861                let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
862
863                update::items_rev(previous, padding, sizes_below_incl, sizes_above);
864            } else {
865                let sizes_below_incl =
866                    buffer.sizes_lower_inclusive(self.selection, &mut self.below);
867                let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
868
869                update::items(previous, padding, sizes_below_incl, sizes_above);
870            }
871        } else {
872            self.below.clear();
873            self.above.clear();
874            self.selection = 0;
875        }
876    }
877
878    #[inline]
879    pub fn set_selection(&mut self, new_selection: u32) -> bool {
880        let buffer = self.nucleo.snapshot();
881        let new_selection = new_selection.min(buffer.total().saturating_sub(1));
882
883        let previous = self.state();
884        let padding = self.padding(self.size);
885
886        if new_selection == 0 {
887            self.reset()
888        } else if new_selection > self.selection {
889            if self.config.reversed {
890                let sizes_below_incl =
891                    buffer.sizes_higher_inclusive(new_selection, &mut self.below);
892                let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
893
894                selection::incr_rev(
895                    previous,
896                    new_selection,
897                    padding,
898                    padding,
899                    sizes_below_incl,
900                    sizes_above,
901                );
902            } else {
903                let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
904                let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
905
906                selection::incr(
907                    previous,
908                    new_selection,
909                    padding,
910                    sizes_below_incl,
911                    sizes_above,
912                );
913            }
914
915            self.selection = new_selection;
916
917            true
918        } else if new_selection < self.selection {
919            if self.config.reversed {
920                let sizes_below_incl =
921                    buffer.sizes_higher_inclusive(new_selection, &mut self.below);
922                let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
923
924                selection::decr_rev(
925                    previous,
926                    new_selection,
927                    padding,
928                    sizes_below_incl,
929                    sizes_above,
930                );
931            } else {
932                let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
933                let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
934
935                selection::decr(
936                    previous,
937                    new_selection,
938                    padding,
939                    padding,
940                    sizes_below_incl,
941                    sizes_above,
942                );
943            }
944
945            self.selection = new_selection;
946
947            true
948        } else {
949            false
950        }
951    }
952
953    /// Increment the selection by the given amount.
954    #[cfg(test)]
955    pub fn selection_incr(&mut self, increase: u32) -> bool {
956        let new_selection = self
957            .selection
958            .saturating_add(increase)
959            .min(self.nucleo.snapshot().total().saturating_sub(1));
960
961        self.set_selection(new_selection)
962    }
963
964    /// Decrement the selection by the given amount.
965    #[cfg(test)]
966    pub fn selection_decr(&mut self, decrease: u32) -> bool {
967        let new_selection = self.selection.saturating_sub(decrease);
968
969        self.set_selection(new_selection)
970    }
971}