Skip to main content

nucleo_picker/
lib.rs

1//! # A generic fuzzy item picker
2//! This crate contains a generic picker implementation that allows you to natively incorporate an
3//! interactive fuzzy picker TUI (similar in spirit to the very popular
4//! [fzf](https://github.com/junegunn/fzf)) directly in your own applications.
5//!
6//! In short, initialize a [`Picker`] using [`PickerOptions`] and describe how the items
7//! should be represented by implementing [`Render`], or use a [built-in renderer](render).
8//!
9//! For more complex use-cases and integration with an existing application, see the
10//! [`event`] module.
11//!
12//! ## Usage examples
13//! For more usage examples, visit the [examples
14//! folder](https://github.com/autobib/nucleo-picker/tree/master/examples) on GitHub.
15//!
16//! ### `fzf` example
17//! Run this example with `cat myfile.txt | cargo run --release --example fzf_basic`.
18//! ```no_run
19#![doc = include_str!("../examples/fzf_basic.rs")]
20//! ```
21//!
22//! ### `find` example
23//! Run this example with `cargo run --release --example find ~`.
24//! ```no_run
25#![doc = include_str!("../examples/find.rs")]
26//! ```
27
28#![deny(missing_docs)]
29#![warn(rustdoc::unescaped_backticks)]
30#![cfg_attr(docsrs, feature(doc_cfg))]
31
32mod component;
33pub mod error;
34pub mod event;
35mod incremental;
36mod injector;
37mod lazy;
38mod match_list;
39mod observer;
40mod prompt;
41pub mod render;
42mod util;
43
44use std::{
45    borrow::Cow,
46    io::{self, BufWriter, IsTerminal, Write},
47    iter::Extend,
48    num::NonZero,
49    panic::{set_hook, take_hook},
50    sync::Arc,
51    thread::available_parallelism,
52    time::{Duration, Instant},
53};
54
55use crossterm::{
56    QueueableCommand,
57    cursor::MoveTo,
58    event::{DisableBracketedPaste, EnableBracketedPaste, KeyEvent},
59    execute,
60    terminal::{
61        BeginSynchronizedUpdate, EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen,
62        disable_raw_mode, enable_raw_mode, size,
63    },
64};
65use nucleo::{
66    self as nc, Nucleo,
67    pattern::{CaseMatching as NucleoCaseMatching, Normalization as NucleoNormalization},
68};
69use observer::{Notifier, Observer};
70
71use crate::{
72    component::Status,
73    error::PickError,
74    event::{Event, EventSource, RecvError, StdinReader, keybind_default, keybind_no_multi},
75    lazy::{LazyMatchList, LazyPrompt},
76    match_list::{MatchList, MatchListConfig, Queued, SelectedIndices},
77    prompt::{Prompt, PromptConfig},
78};
79
80pub use crate::injector::Injector;
81pub use crate::match_list::Selection;
82pub use nucleo;
83
84/// A trait which describes how to render objects for matching and display.
85///
86/// Some renderers for common types are already implemented in the [`render`] module. In
87/// many cases, the [`DisplayRenderer`](render::DisplayRenderer) is particularly easy to use.
88/// This trait is also automatically implemented for [closures which return `Cow<'a,
89/// str>`](#impl-Render<T>-for-R).
90///
91/// Rendering *must* be **pure**: for a given render implementation `R` and a item `T`, the call
92/// `R::render(&self, &T)` must depend only on the specific render instance and the specific item,
93/// and not any other state. Violation of this condition is normally only possible via interior
94/// mutability, global state, I/O, or unsafe code.
95///
96/// If purism is violated, internal index computations which depend on the rendered format
97/// will become invalid and the picker may panic or return incorrect results. Note that such
98/// errors are encapsulated within the picker and will not result in undefined behaviour.
99///
100/// ## Examples
101/// Here is a basic example for how one would implement a renderer for a `DirEntry` from the
102/// [ignore](https://docs.rs/ignore/latest/ignore/) crate.
103/// ```
104/// use std::borrow::Cow;
105///
106/// use nucleo_picker::Render;
107/// use ignore::DirEntry;
108///
109/// pub struct DirEntryRenderer;
110///
111/// impl Render<DirEntry> for DirEntryRenderer {
112///     type Str<'a> = Cow<'a, str>;
113///
114///     fn render<'a>(&self, item: &'a DirEntry) -> Self::Str<'a> {
115///         item.path().to_string_lossy()
116///     }
117/// }
118/// ```
119/// Here is another example showing that a renderer can use internal (immutable) state to customize
120/// the rendered format.
121/// ```
122/// use nucleo_picker::Render;
123///
124/// pub struct PrefixRenderer {
125///     prefix: String,
126/// }
127///
128/// impl<T: AsRef<str>> Render<T> for PrefixRenderer {
129///     type Str<'a> = String
130///         where T: 'a;
131///
132///     fn render<'a>(&self, item: &'a T) -> Self::Str<'a> {
133///         let mut rendered = String::new();
134///         rendered.push_str(&self.prefix);
135///         rendered.push_str(item.as_ref());
136///         rendered
137///     }
138/// }
139/// ```
140///
141/// ## Render considerations
142/// The picker is capable of correctly displaying most Unicode data. Internally, Unicode width
143/// calculations are performed to keep track of the amount of space that it takes on the screen to
144/// display a given item.
145///
146/// The main exeption is control characters which are not newlines (`\n` or `\r\n`). Even visible
147/// control characters such as tabs (`\t`) will cause issues: width calculations will most likely
148/// be incorrect since the amount of space a tab occupies depends on its position within the
149/// screen.
150///
151/// It is best to avoid such characters in your rendered format. If you do not have control
152/// over the incoming data, the most robust solution is likely to perform substitutions while
153/// rendering.
154/// ```
155/// # use nucleo_picker::Render;
156/// use std::borrow::Cow;
157///
158/// fn renderable(c: char) -> bool {
159///     !c.is_control() || c == '\n'
160/// }
161///
162/// struct ControlReplaceRenderer;
163///
164/// impl<T: AsRef<str>> Render<T> for ControlReplaceRenderer {
165///     type Str<'a>
166///         = Cow<'a, str>
167///     where
168///         T: 'a;
169///
170///     fn render<'a>(&self, item: &'a T) -> Self::Str<'a> {
171///         let mut str = Cow::Borrowed(item.as_ref());
172///
173///         if str.contains(|c| !renderable(c)) {
174///             str.to_mut().retain(renderable);
175///         }
176///
177///         str
178///     }
179/// }
180/// ```
181///
182/// ## Performance considerations
183/// In the majority of situations, performance of the [`Render`] implementation is only relevant
184/// when sending the items to the picker, and not for generating the match list interactively. In
185/// particular, in the majority of situations, [`Render::render`] is called exactly once per item
186/// when it is sent to the picker.
187///
188/// The **only** exception to this rule occurs the value returned by [`Render::render`] contains
189/// non-ASCII characters. In this situation, it can happen that *exceptionally slow* [`Render`]
190/// implementations will reduce interactivity. A crude rule of thumb is that rendering a single
191/// item should take (in the worst case) at most 100μs. For comparison, display formatting a
192/// `f64` takes around 100ns.
193///
194/// 100μs is an extremely large amount of time in the vast majority of situations. If after
195/// benchmarking you determine that this is not the case for your [`Render`] implementation,
196/// and moreover your [`Render`] implementation outputs (in the majority of cases) non-ASCII
197/// Unicode, you can internally cache the render computation (at the cost of increased memory
198/// overhead):
199/// ```
200/// # use nucleo_picker::Render;
201/// pub struct Item<D> {
202///     data: D,
203///     /// the pre-computed rendered version of `data`
204///     rendered: String,
205/// }
206///
207/// pub struct ItemRenderer;
208///
209/// impl<D> Render<Item<D>> for ItemRenderer {
210///     type Str<'a>
211///         = &'a str
212///     where
213///         D: 'a;
214///
215///     fn render<'a>(&self, item: &'a Item<D>) -> Self::Str<'a> {
216///         &item.rendered
217///     }
218/// }
219/// ```
220pub trait Render<T> {
221    /// The string type that `T` is rendered as, most commonly a [`&'a str`](str), a
222    /// [`Cow<'a, str>`](std::borrow::Cow), or a [`String`].
223    type Str<'a>: AsRef<str>
224    where
225        T: 'a;
226
227    /// Render the given item as it should appear in the picker. See the
228    /// [trait-level docs](Render) for more detail.
229    fn render<'a>(&self, item: &'a T) -> Self::Str<'a>;
230}
231
232impl<T, R: for<'a> Fn(&'a T) -> Cow<'a, str>> Render<T> for R {
233    type Str<'a>
234        = Cow<'a, str>
235    where
236        T: 'a;
237
238    fn render<'a>(&self, item: &'a T) -> Self::Str<'a> {
239        self(item)
240    }
241}
242
243#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
244#[non_exhaustive]
245/// How to treat a case mismatch between two characters.
246pub enum CaseMatching {
247    /// Characters never match their case folded version (`a != A`).
248    Respect,
249    /// Characters always match their case folded version (`a == A`).
250    Ignore,
251    /// Act like [`Ignore`](CaseMatching::Ignore) if all characters in a pattern atom are
252    /// lowercase and like [`Respect`](CaseMatching::Respect) otherwise.
253    #[default]
254    Smart,
255}
256
257impl CaseMatching {
258    pub(crate) const fn convert(self) -> NucleoCaseMatching {
259        match self {
260            Self::Respect => NucleoCaseMatching::Respect,
261            Self::Ignore => NucleoCaseMatching::Ignore,
262            Self::Smart => NucleoCaseMatching::Smart,
263        }
264    }
265}
266
267#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
268#[non_exhaustive]
269/// How to handle Unicode Latin normalization.
270pub enum Normalization {
271    /// Characters never match their normalized version (`a != ä`).
272    Never,
273    /// Act like [`Never`](Normalization::Never) if any character would need to be normalized, and
274    /// otherwise perform normalization (`a == ä` but `ä != a`).
275    #[default]
276    Smart,
277}
278
279impl Normalization {
280    pub(crate) const fn convert(self) -> NucleoNormalization {
281        match self {
282            Self::Never => NucleoNormalization::Never,
283            Self::Smart => NucleoNormalization::Smart,
284        }
285    }
286}
287
288/// Specify configuration options for a [`Picker`].
289///
290/// Initialize with [`new`](PickerOptions::new) or (equivalently) the
291/// [`Default`](PickerOptions::default) implementation, specify options, and then convert to a
292/// [`Picker`] using the [`picker`](PickerOptions::picker) method.
293///
294/// ## Example
295/// ```
296/// use nucleo_picker::{render::StrRenderer, Picker, PickerOptions};
297///
298/// let picker: Picker<String, _> = PickerOptions::new()
299///     .highlight(true)
300///     .query("search")
301///     .picker(StrRenderer);
302/// ```
303///
304/// ## Sort order settings
305///
306/// There are three settings which influence the order in which items appear on the screen.
307///
308/// The [`reversed`](Self::reversed) setting only influences the layout: by default, the query is
309/// placed at the bottom of the screen, with the match list rendered bottom-up. If this is set, the query will be placed at the top of the screen, with the match list rendered top-down.
310///
311/// The [`reverse_items`](Self::reverse_items) and [`sort_results`](Self::sort_results) are used to
312/// determine *the order of the items inside the match list*. The order is defined according to the
313/// following table. Here, `Index` refers to the order in which the `picker` received the items
314/// from the [`Injector`]s. If you use exactly one injector, this is guaranteed to be the same as
315/// the insertion order.
316///
317/// | `sort_results` | `reverse_items` | Sort priority                              |
318/// |----------------|-----------------|--------------------------------------------|
319/// | `true`         | `false`         | Score (desc) → Length (asc) → Index (asc)  |
320/// | `true`         | `true`          | Score (desc) → Length (asc) → Index (desc) |
321/// | `false`        | `false`         | Index (asc)                                |
322/// | `false`        | `true`          | Index (desc)                               |
323pub struct PickerOptions {
324    config: nc::Config,
325    query: String,
326    threads: Option<NonZero<usize>>,
327    max_selection_count: Option<NonZero<u32>>,
328    interval: Duration,
329    match_list_config: MatchListConfig,
330    prompt_config: PromptConfig,
331    sort_results: bool,
332    reverse_items: bool,
333}
334
335impl Default for PickerOptions {
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341impl PickerOptions {
342    /// Initialize with default configuration.
343    ///
344    /// Equivalent to the [`Default`] implementation, but as a `const fn`.
345    #[must_use]
346    #[inline]
347    pub const fn new() -> Self {
348        Self {
349            config: nc::Config::DEFAULT,
350            query: String::new(),
351            threads: None,
352            max_selection_count: None,
353            interval: Duration::from_millis(15),
354            match_list_config: MatchListConfig::new(),
355            prompt_config: PromptConfig::new(),
356            sort_results: true,
357            reverse_items: false,
358        }
359    }
360
361    /// Convert into a [`Picker`].
362    #[must_use]
363    pub fn picker<T: Send + Sync + 'static, R>(self, render: R) -> Picker<T, R> {
364        let engine = Nucleo::with_match_list_config(
365            self.config.clone(),
366            Arc::new(|| {}),
367            // nucleo's API is a bit weird here in that it does not accept `NonZero<usize>`
368            self.threads
369                .or_else(|| {
370                    // Reserve two threads:
371                    // 1. for populating the matcher
372                    // 2. for rendering the terminal UI and handling user input
373                    available_parallelism()
374                        .ok()
375                        .and_then(|it| it.get().checked_sub(2).and_then(NonZero::new))
376                })
377                .map(NonZero::get),
378            1,
379            nc::MatchListConfig {
380                sort_results: self.sort_results,
381                reverse_items: self.reverse_items,
382            },
383        );
384
385        let reversed = self.match_list_config.reversed;
386
387        let mut match_list =
388            MatchList::new(self.match_list_config, self.config, engine, render.into());
389
390        let mut prompt = Prompt::new(self.prompt_config);
391
392        // set the prompt
393        match_list.reparse(&self.query);
394        prompt.set_query(self.query);
395
396        Picker {
397            match_list,
398            prompt,
399            interval: self.interval,
400            max_selection_count: self.max_selection_count,
401            reversed,
402            restart_notifier: None,
403        }
404    }
405
406    /// Set 'reversed' layout.
407    ///
408    /// Option `false` (default) will put the prompt at the bottom and render items in ascending
409    /// order. Option `true` will put the prompt at the top and render items in descending order.
410    #[must_use]
411    #[inline]
412    pub const fn reversed(mut self, reversed: bool) -> Self {
413        self.match_list_config.reversed = reversed;
414        self
415    }
416
417    /// Reverse the item insert order.
418    ///
419    /// This changes the index tie-break method to prefer later indices rather than earlier
420    /// indices. This option is typically used with [`sort_results`](Self::sort_results) set
421    /// to `false`, in which case the newest items sent to the picker will be placed
422    /// first, rather than last.
423    ///
424    /// The default is `false`.
425    #[must_use]
426    #[inline]
427    pub const fn reverse_items(mut self, reversed: bool) -> Self {
428        self.reverse_items = reversed;
429        self
430    }
431
432    /// Whether or not to sort matching items by score.
433    ///
434    /// This option is useful when you just want to filter items, but preserve the original order.
435    /// By default, the oldest items will appear at the beginning and the newest items will appear at the end.
436    /// This can be swapped by setting [`reverse_items`](Self::reverse_items) to `true`.
437    #[must_use]
438    #[inline]
439    pub const fn sort_results(mut self, sort: bool) -> Self {
440        self.sort_results = sort;
441        self
442    }
443
444    /// The maximum number of items that can be selected in the picker.
445    ///
446    /// If `None`, no bound is applied; otherwise, use the provided bound. This is a `u32` since
447    /// the picker cannot hold more than [`u32::MAX`] items.
448    ///
449    /// This bound is only relevant when allowing [multiple selections](Picker#multiple-selections)
450    /// and has no impact on single selection mode. The returned [`Selection`] will contain at
451    /// most `maximum` items. The picker will not allow the selection of additional items if the maximum
452    /// is reached.
453    ///
454    /// The default is `None`.
455    #[must_use]
456    #[inline]
457    pub const fn max_selection_count(mut self, maximum: Option<NonZero<u32>>) -> Self {
458        self.max_selection_count = maximum;
459        self
460    }
461
462    /// Set how long each frame should last.
463    ///
464    /// This is the reciprocal of the refresh rate. The default value is
465    /// `Duration::from_millis(15)`, which corresponds to a refresh rate of approximately 67 frames
466    /// per second. It is not recommended to set this to a value less than 8ms (approximately 125
467    /// frames per second).
468    #[must_use]
469    #[inline]
470    pub const fn frame_interval(mut self, interval: Duration) -> Self {
471        self.interval = interval;
472        self
473    }
474
475    /// Set the number of threads used by the internal matching engine.
476    ///
477    /// If `None` (default), use a heuristic choice based on the amount of available
478    /// parallelism along with other factors.
479    #[must_use]
480    #[inline]
481    pub const fn threads(mut self, threads: Option<NonZero<usize>>) -> Self {
482        self.threads = threads;
483        self
484    }
485
486    /// Set the internal match engine configuration (default to [`nucleo::Config::DEFAULT`]).
487    #[must_use]
488    #[inline]
489    #[deprecated(
490        since = "0.10.0",
491        note = "Use native methods `prefer_prefix` and `match_paths`. The `normalize` and `ignore_case` settings are never used; use `normalization` and `case_matching` instead."
492    )]
493    pub fn config(mut self, config: nc::Config) -> Self {
494        self.config = config;
495        self
496    }
497
498    /// How to perform Unicode normalization (defaults to [`Normalization::Smart`]).
499    #[must_use]
500    #[inline]
501    pub const fn normalization(mut self, normalization: Normalization) -> Self {
502        self.match_list_config.normalization = normalization.convert();
503        self
504    }
505
506    /// How to treat case mismatch (defaults to [`CaseMatching::Smart`]).
507    #[must_use]
508    #[inline]
509    pub const fn case_matching(mut self, case_matching: CaseMatching) -> Self {
510        self.match_list_config.case_matching = case_matching.convert();
511        self
512    }
513
514    /// Enable score bonuses appropriate for matching file paths.
515    #[must_use]
516    #[inline]
517    pub const fn match_paths(mut self) -> Self {
518        self.config = self.config.match_paths();
519        self
520    }
521
522    /// Whether to provide a bonus to matches by their distance from the start of the item.
523    ///
524    /// This is disabled by default and only recommended for autocompletion use-cases, where the expectation is that the user is typing the entire match.
525    #[must_use]
526    #[inline]
527    pub const fn prefer_prefix(mut self, prefer_prefix: bool) -> Self {
528        self.config.prefer_prefix = prefer_prefix;
529        self
530    }
531
532    /// Whether or not to highlight matches (default to `true`).
533    #[must_use]
534    #[inline]
535    pub const fn highlight(mut self, highlight: bool) -> Self {
536        self.match_list_config.highlight = highlight;
537        self
538    }
539
540    /// How much space to leave when rendering match highlighting (default to `3`).
541    #[must_use]
542    #[inline]
543    pub const fn highlight_padding(mut self, size: u16) -> Self {
544        self.match_list_config.highlight_padding = size;
545        self
546    }
547
548    /// How much space to leave around the selection when scrolling (default to `3`).
549    #[must_use]
550    #[inline]
551    pub const fn scroll_padding(mut self, size: u16) -> Self {
552        self.match_list_config.scroll_padding = size;
553        self
554    }
555
556    /// How much space to leave around the cursor (default to `2`).
557    #[must_use]
558    #[inline]
559    pub const fn prompt_padding(mut self, size: u16) -> Self {
560        self.prompt_config.padding = size;
561        self
562    }
563
564    /// Provide an initial query string for the prompt (default to `""`).
565    #[must_use]
566    #[inline]
567    pub fn query<Q: Into<String>>(mut self, query: Q) -> Self {
568        self.query = query.into();
569        self
570    }
571}
572
573/// A fuzzy matching interactive item picker.
574///
575/// The parameter `T` is the item type and the parameter `R` is the [renderer](Render), which
576/// describes how to represent `T` in the match list.
577///
578/// Initialize a picker with [`Picker::new`], or with custom configuration using
579/// [`PickerOptions`], and add elements to the picker using an [`Injector`] returned
580/// by the [`Picker::injector`] method.
581/// ```
582/// use nucleo_picker::{render::StrRenderer, Picker};
583///
584/// // Initialize a picker using default settings, with item type `String`
585/// let picker: Picker<String, _> = Picker::new(StrRenderer);
586/// ```
587///
588/// See also the [usage
589/// examples](https://github.com/autobib/nucleo-picker/tree/master/examples).
590///
591/// ## Picker variants
592///
593/// The picker can be run in a number of different modes.
594///
595/// 1. The simplest (and most common) method is to use [`Picker::pick`].
596/// 2. If you wish to customize keybindings, use [`Picker::pick_with_keybind`].
597/// 3. If you wish to customize all IO to the picker, use [`Picker::pick_with_io`].
598///
599/// These methods return `Option<&T>` as the return type, where `None` indicates that no items were
600/// selected.
601///
602/// ### Multiple selections
603///
604/// If you wish to permit the user to make multiple selections, use one of the similarly named
605/// methods:
606///
607/// 1. [`Picker::pick_multi`]
608/// 2. [`Picker::pick_multi_with_keybind`]
609/// 3. [`Picker::pick_multi_with_io`]
610///
611/// These methods are analogous to their single-selection variants, except additional items can be
612/// queued with the [`MatchListEvent::ToggleDown`](crate::event::MatchListEvent::ToggleDown) and
613/// [`MatchListEvent::ToggleUp`](crate::event::MatchListEvent::ToggleUp) events. The [default
614/// keybindings](keybind_default) bind these to `⇥` and `shift + ⇥` respectively. In this case, an
615/// [`Event::Select`] is handled slightly differently: if there are no queued selections, this
616/// picks the highlighted item, but if there are queued selections, then only the queued selections
617/// are returned.
618///
619/// If the picker [restarts while running](Event::Restart), the queued item list will be cleared
620/// since the previous items are removed.
621///
622/// The selected items are returned as a [`Selection`], which is empty if picker exited with
623/// [`Event::Quit`] (or [`Event::QuitPromptEmpty`]), and non-empty if not.
624///
625/// ### Emulate single selection using a multi-picker
626///
627/// It is possible to emulate single selection with one of the multi-picker methods by setting
628/// [`PickerOptions::max_selection_count`] to `Some(1)`. This will force the resulting
629/// [`Selection`] to contain either 0 or 1 element, and you can convert to `Option<&T>` by calling
630/// `next` on the [iterator](Selection::iter).
631///
632/// Note that the picker interface will be slightly different: it is still possible to
633/// queue at most one picked item using `⇥`. With the non-multi-pickers, it is not possible to
634/// queue items at all.
635///
636/// ## A note on memory usage
637/// Initializing a picker is a relatively expensive operation since the internal match engine uses
638/// an arena-based memory approach to minimize allocator costs, and this memory is initialized when
639/// the picker is created.
640///
641/// To re-use the picker without additional start-up costs, use [`Picker::restart`].
642///
643/// # Example
644/// Run the picker on [`Stdout`](std::io::Stdout) with no interactivity checks, and quitting
645/// gracefully on `ctrl + c`.
646/// ```no_run
647#[doc = include_str!("../examples/custom_io.rs")]
648/// ```
649pub struct Picker<T: Send + Sync + 'static, R> {
650    match_list: MatchList<T, R>,
651    max_selection_count: Option<NonZero<u32>>,
652    prompt: Prompt,
653    interval: Duration,
654    reversed: bool,
655    restart_notifier: Option<Notifier<Injector<T, R>>>,
656}
657
658impl<T: Send + Sync + 'static, R: Render<T>> Extend<T> for Picker<T, R> {
659    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
660        let injector = self.injector();
661        for it in iter {
662            injector.push(it);
663        }
664    }
665}
666
667impl<T: Send + Sync + 'static, R> Picker<T, R> {
668    /// Initialize a new picker with default configuration and the provided renderer.
669    #[must_use]
670    pub fn new(render: R) -> Self
671    where
672        R: Render<T>,
673    {
674        PickerOptions::default().picker(render)
675    }
676
677    /// Update the default query string. This is mainly useful for modifying the query string
678    /// before re-using the [`Picker`].
679    ///
680    /// See the [`PickerOptions::query`] method to set the query during initialization, and
681    /// [`PromptEvent::Reset`](event::PromptEvent::Reset) to reset the query during interactive
682    /// use.
683    #[inline]
684    pub fn update_query<Q: Into<String>>(&mut self, query: Q) {
685        self.prompt.set_query(query);
686        self.match_list.reparse(self.prompt.contents());
687    }
688
689    /// Returns the contents of the query string internal to the picker.
690    ///
691    /// If called after running `Picker::pick`, this will contain the contents of the query string
692    /// at the moment that the item was selected or the picker quit.
693    #[must_use]
694    pub fn query(&self) -> &str {
695        self.prompt.contents()
696    }
697
698    /// Returns an [`Observer`] containing up-to-date [`Injector`]s for this picker.
699    ///
700    /// This is the channel to which new injectors will be sent when the picker processes a
701    /// [restart event](Event::Restart). Restart events are not generated by this library. You only
702    /// need this channel if you generate restart events in your own code. See the [`Event`] documentation
703    /// for more detail.
704    ///
705    /// Calling this method will *invalidate all earlier observers*. If you want multiple copies of
706    /// the same observer, clone your existing observer.
707    ///
708    /// If `with_injector` is `true`, the channel is intialized with an injector currently valid
709    /// for the picker on creation.
710    #[must_use]
711    pub fn injector_observer(&mut self, with_injector: bool) -> Observer<Injector<T, R>> {
712        let (notifier, observer) = if with_injector {
713            observer::occupied_channel(self.injector())
714        } else {
715            observer::channel()
716        };
717        self.restart_notifier = Some(notifier);
718        observer
719    }
720
721    /// Update the internal nucleo configuration.
722    #[inline]
723    pub fn update_config(&mut self, config: nc::Config) {
724        self.match_list.update_nucleo_config(config);
725    }
726
727    /// Restart the match engine, disconnecting all active injectors and clearing the existing
728    /// search query.
729    ///
730    /// All items are removed immediately. Existing injectors will continue to function but the
731    /// items will no longer be received by this instance. The old items will only be dropped when
732    /// all injectors are dropped.
733    ///
734    /// This method is mainly useful for re-using the picker for multiple matches since the
735    /// internal memory buffers are preserved. To restart the picker during interactive use, see
736    /// the [`Event`] documentation or the [restart
737    /// example](https://github.com/autobib/nucleo-picker/blob/master/examples/restart.rs).
738    pub fn restart(&mut self) {
739        self.match_list.restart();
740        self.update_query("");
741    }
742
743    /// Restart the match engine, disconnecting all active injectors and replacing the internal
744    /// renderer.
745    ///
746    /// The provided [`Render`] implementation must be the same type as the one originally
747    /// provided; this is most useful for stateful renderers.
748    ///
749    /// See [`Picker::restart`] for more detail. Note that method *does not* clear the query.
750    pub fn reset_renderer(&mut self, render: R) {
751        self.match_list.reset_renderer(render);
752    }
753
754    /// Get an [`Injector`] to send items to the picker.
755    #[must_use]
756    pub fn injector(&self) -> Injector<T, R> {
757        self.match_list.injector()
758    }
759
760    /// A convenience method to add a batch of items directly to the picker.
761    ///
762    /// The number of items in the iterator must be known exactly. This is a convenience wrapper
763    /// around [`Injector::extend_exact`].
764    pub fn extend_exact<I>(&self, iter: I)
765    where
766        R: Render<T>,
767        I: IntoIterator<Item = T>,
768        <I as IntoIterator>::IntoIter: ExactSizeIterator,
769    {
770        self.injector().extend_exact(iter);
771    }
772
773    /// A convenience method to obtain the rendered version of an item as it would appear in the
774    /// picker.
775    ///
776    /// This is the same as calling [`Render::render`] on the [`Render`] implementation internal
777    /// to the picker.
778    #[inline]
779    pub fn render<'a>(&self, item: &'a T) -> <R as Render<T>>::Str<'a>
780    where
781        R: Render<T>,
782    {
783        self.match_list.render(item)
784    }
785
786    /// Open the interactive picker prompt and return the picked item, if any.
787    ///
788    /// ## Stderr lock
789    /// The picker prompt is rendered in an alternate screen using the `stderr` file handle. In
790    /// order to prevent screen corruption, a lock is acquired to `stderr`; see
791    /// [`StderrLock`](std::io::StderrLock) for more detail.
792    ///
793    /// In particular, while the picker is interactive, any other thread which attempts to write to
794    /// stderr will block. Note that `stdin` and `stdout` will remain fully interactive.
795    ///
796    /// ## IO customization
797    ///
798    /// To further customize the IO behaviour of the picker, such as to provide your own writer
799    /// (for instance to write to [`Stdout`](std::io::Stdout) instead) or use custom keybindings,
800    /// see the [`pick_with_io`](Self::pick_with_io)  and
801    /// [`pick_with_keybind`](Self::pick_with_keybind) methods.
802    ///
803    /// # Errors
804    /// Underlying IO errors from the standard library or [`crossterm`] will be propagated with the
805    /// [`PickError::IO`] variant.
806    ///
807    /// This method also fails with:
808    ///
809    /// 1. [`PickError::NotInteractive`] if stderr is not interactive.
810    /// 2. [`PickError::UserInterrupted`] if the user presses `ctrl + c`.
811    ///
812    /// This method will **never** return [`PickError::Disconnected`].
813    #[inline]
814    pub fn pick(&mut self) -> Result<Option<&T>, PickError>
815    where
816        R: Render<T>,
817    {
818        self.pick_with_keybind(keybind_no_multi)
819    }
820
821    /// Open the interactive picker prompt and return the picked items, if any.
822    ///
823    /// This method permits the user to select multiple items, but is otherwise identical to [`pick`](Self::pick). See those docs as well as the
824    /// [docs on multiple selections](Picker#multiple-selections) for more detail.
825    #[inline]
826    pub fn pick_multi(&mut self) -> Result<Selection<'_, T>, PickError>
827    where
828        R: Render<T>,
829    {
830        self.pick_multi_with_keybind(keybind_default)
831    }
832
833    /// Open the interactive picker prompt and return the picked item, if any. The provided
834    /// keybindings are used in the interactive picker.
835    ///
836    /// The picker prompt is rendered in an alternate screen using the `stderr` file handle. See
837    /// the [`pick`](Self::pick) method for more detail.
838    ///
839    /// To further customize event generation, see the [`pick_with_io`](Self::pick_with_io) method.
840    /// The [`pick`](Self::pick) method is internally a call to this method with keybindings
841    /// provided by [`keybind_default`].
842    ///
843    /// # Errors
844    ///
845    /// Underlying IO errors from the standard library or [`crossterm`] will be propagated with the
846    /// [`PickError::IO`] variant.
847    ///
848    /// This method also fails with:
849    ///
850    /// 1. [`PickError::NotInteractive`] if stderr is not interactive.
851    /// 2. [`PickError::UserInterrupted`] if a keybinding results in a [`Event::UserInterrupt`],
852    ///
853    /// This method will **never** return [`PickError::Disconnected`].
854    #[inline]
855    pub fn pick_with_keybind<F>(&mut self, keybind: F) -> Result<Option<&T>, PickError>
856    where
857        R: Render<T>,
858        F: FnMut(KeyEvent) -> Option<Event>,
859    {
860        let stderr = io::stderr().lock();
861        if stderr.is_terminal() {
862            self.pick_with_io(StdinReader::new(keybind), &mut BufWriter::new(stderr))
863        } else {
864            Err(PickError::NotInteractive)
865        }
866    }
867
868    /// Open the interactive picker prompt and return the picked item, if any. The provided
869    /// keybindings are used in the interactive picker.
870    ///
871    ///
872    /// This method permits the user to select multiple items, but is otherwise identical to [`pick_with_keybind`](Self::pick_with_keybind). See those docs as well as the
873    /// [docs on multiple selections](Picker#multiple-selections) for more detail.
874    #[inline]
875    pub fn pick_multi_with_keybind<F>(&mut self, keybind: F) -> Result<Selection<'_, T>, PickError>
876    where
877        R: Render<T>,
878        F: FnMut(KeyEvent) -> Option<Event>,
879    {
880        let stderr = io::stderr().lock();
881        if stderr.is_terminal() {
882            self.pick_multi_with_io(StdinReader::new(keybind), &mut BufWriter::new(stderr))
883        } else {
884            Err(PickError::NotInteractive)
885        }
886    }
887
888    /// Run the picker interactively with a custom event source and writer.
889    ///
890    /// The picker is rendered using the given writer. In most situations, you want to check that
891    /// the writer is interactive using, for instance, [`IsTerminal`]. The picker reads
892    /// events from the [`EventSource`] to update the screen. See the docs for [`EventSource`]
893    /// for more detail.
894    ///
895    /// # Errors
896    /// Underlying IO errors from the standard library or [`crossterm`] will be propagated with the
897    /// [`PickError::IO`] variant.
898    ///
899    /// Whether or not this fails with another [`PickError`] variant depends on the [`EventSource`]
900    /// implementation:
901    ///
902    /// 1. If [`EventSource::recv_timeout`] fails with a [`RecvError::Disconnected`], the error
903    ///    returned will be [`PickError::Disconnected`].
904    /// 2. The error will be [`PickError::UserInterrupted`] if the [`Picker`] receives an
905    ///    [`Event::UserInterrupt`].
906    /// 3. The error will be [`PickError::Aborted`] if the [`Picker`] receives an
907    ///    [`Event::Abort`].
908    ///
909    /// This method will **never** return [`PickError::NotInteractive`] since interactivity checks
910    /// are not done.
911    pub fn pick_with_io<E, W>(
912        &mut self,
913        event_source: E,
914        writer: &mut W,
915    ) -> Result<Option<&T>, PickError<<E as EventSource>::AbortErr>>
916    where
917        R: Render<T>,
918        E: EventSource,
919        W: io::Write,
920    {
921        self.pick_impl::<_, _, ()>(event_source, writer)
922    }
923
924    /// Run the picker interactively with a custom event source and writer, allowing the user to
925    /// select multiple items.
926    ///
927    /// This is otherwise identical to [`pick_with_io`](Self::pick_with_io); see those docs as well
928    /// as the [docs on multiple selections](Picker#multiple-selections) for more detail.
929    pub fn pick_multi_with_io<E, W>(
930        &mut self,
931        event_source: E,
932        writer: &mut W,
933    ) -> Result<Selection<'_, T>, PickError<<E as EventSource>::AbortErr>>
934    where
935        R: Render<T>,
936        E: EventSource,
937        W: io::Write,
938    {
939        self.pick_impl::<_, _, SelectedIndices>(event_source, writer)
940    }
941
942    /// Initialize the alternate screen.
943    #[inline]
944    fn init_screen<W: Write>(writer: &mut W) -> io::Result<()> {
945        enable_raw_mode()?;
946        execute!(writer, EnterAlternateScreen, EnableBracketedPaste)?;
947        Ok(())
948    }
949
950    /// Cleanup the alternate screen when finished.
951    #[inline]
952    fn cleanup_screen<W: Write>(writer: &mut W) -> io::Result<()> {
953        disable_raw_mode()?;
954        execute!(writer, DisableBracketedPaste, LeaveAlternateScreen)?;
955        Ok(())
956    }
957
958    /// Render the frame, specifying which parts of the frame need to be re-drawn.
959    #[inline]
960    fn render_frame<W: Write, Q: Queued>(
961        &mut self,
962        writer: &mut W,
963        redraw_prompt: bool,
964        redraw_match_list: bool,
965        queued_items: &Q,
966    ) -> io::Result<()>
967    where
968        R: Render<T>,
969    {
970        let (width, height) = size()?;
971
972        let (prompt_row, match_list_row) = if self.reversed {
973            (0, 1)
974        } else {
975            (height - 1, 0)
976        };
977
978        if width >= 1 && (redraw_prompt || redraw_match_list) {
979            writer.queue(BeginSynchronizedUpdate)?;
980
981            if redraw_match_list && height >= 2 {
982                writer.queue(MoveTo(0, match_list_row))?;
983
984                self.match_list.draw(
985                    width,
986                    height - 1,
987                    writer,
988                    |idx| queued_items.is_queued(idx),
989                    queued_items.count(self.max_selection_count),
990                )?;
991            }
992
993            if redraw_prompt && height >= 1 {
994                writer.queue(MoveTo(0, prompt_row))?;
995
996                self.prompt.draw(width, 1, writer)?;
997            }
998
999            writer
1000                .queue(MoveTo(self.prompt.screen_offset() + 2, prompt_row))?
1001                .queue(EndSynchronizedUpdate)?;
1002
1003            // flush to terminal
1004            writer.flush()?;
1005        };
1006
1007        Ok(())
1008    }
1009
1010    fn pick_impl<E, W, Q: Queued>(
1011        &mut self,
1012        mut event_source: E,
1013        writer: &mut W,
1014    ) -> Result<Q::Output<'_, T>, PickError<<E as EventSource>::AbortErr>>
1015    where
1016        R: Render<T>,
1017        E: EventSource,
1018        W: io::Write,
1019    {
1020        // set panic hook in case the `Render` implementation panics
1021        let original_hook = take_hook();
1022        set_hook(Box::new(move |panic_info| {
1023            // intentionally ignore errors here since we're already panicking
1024            let _ = Self::cleanup_screen(&mut io::stderr());
1025            original_hook(panic_info);
1026        }));
1027
1028        let mut queued_items = Q::init(self.max_selection_count);
1029
1030        Self::init_screen(writer)?;
1031
1032        let mut frame_start = Instant::now();
1033
1034        // render the first frame
1035        self.match_list.update(5);
1036        self.render_frame(writer, true, true, &queued_items)?;
1037
1038        let mut redraw_prompt = false;
1039        let mut redraw_match_list = false;
1040
1041        let selection = 'selection: loop {
1042            let mut lazy_match_list = LazyMatchList::new(&mut self.match_list, &mut queued_items);
1043            let mut lazy_prompt = LazyPrompt::new(&mut self.prompt);
1044
1045            // process new events, but do not exceed the frame interval
1046            'event: loop {
1047                match event_source.recv_timeout(frame_start + self.interval - Instant::now()) {
1048                    Ok(event) => match event {
1049                        Event::Prompt(prompt_event) => {
1050                            lazy_prompt.handle(prompt_event);
1051                        }
1052                        Event::MatchList(match_list_event) => {
1053                            lazy_match_list.handle(match_list_event);
1054                        }
1055                        Event::Redraw => {
1056                            redraw_prompt = true;
1057                            redraw_match_list = true;
1058                        }
1059                        Event::Quit => {
1060                            break 'selection Ok(self.match_list.select_none(queued_items));
1061                        }
1062                        Event::QuitPromptEmpty => {
1063                            if lazy_prompt.is_empty() {
1064                                break 'selection Ok(self.match_list.select_none(queued_items));
1065                            }
1066                        }
1067                        Event::Select => {
1068                            if lazy_match_list.has_queued_items() {
1069                                break 'selection Ok(self.match_list.select_queued(queued_items));
1070                            }
1071
1072                            if let Some(n) = lazy_match_list.selection() {
1073                                break 'selection Ok(self.match_list.select_one(queued_items, n));
1074                            }
1075                        }
1076                        Event::Restart => match self.restart_notifier {
1077                            Some(ref notifier) => {
1078                                if notifier.push(lazy_match_list.restart()).is_err() {
1079                                    break 'selection Err(PickError::Disconnected);
1080                                } else {
1081                                    redraw_match_list = true;
1082                                }
1083                            }
1084                            None => break 'selection Err(PickError::Disconnected),
1085                        },
1086                        Event::UserInterrupt => {
1087                            break 'selection Err(PickError::UserInterrupted);
1088                        }
1089                        Event::Abort(err) => {
1090                            break 'selection Err(PickError::Aborted(err));
1091                        }
1092                    },
1093                    Err(RecvError::Timeout) => break 'event,
1094                    Err(RecvError::Disconnected) => {
1095                        break 'selection Err(PickError::Disconnected);
1096                    }
1097                    Err(RecvError::IO(io_err)) => break 'selection Err(PickError::IO(io_err)),
1098                }
1099            }
1100
1101            // we have to set 'frame_start' immediately after processing events, so that the
1102            // render time is also included
1103            frame_start = Instant::now();
1104
1105            // clear out any buffered events
1106            let prompt_status = lazy_prompt.finish();
1107            let match_list_status = lazy_match_list.finish();
1108
1109            // update draw status
1110            redraw_prompt |= prompt_status.needs_redraw();
1111            redraw_match_list |= match_list_status.needs_redraw();
1112
1113            // check if the prompt changed: if so, reparse the match list
1114            if prompt_status.contents_changed {
1115                self.match_list.reparse(self.prompt.contents());
1116                redraw_match_list = true;
1117            }
1118
1119            // update the item list
1120            redraw_match_list |= self
1121                .match_list
1122                .update(2 * self.interval.as_millis() as u64 / 3)
1123                .needs_redraw();
1124
1125            // render the frame
1126            self.render_frame(writer, redraw_prompt, redraw_match_list, &queued_items)?;
1127
1128            // reset the redraw markers
1129            redraw_prompt = false;
1130            redraw_match_list = false;
1131        };
1132
1133        Self::cleanup_screen(writer)?;
1134        selection
1135    }
1136}