nucleo_picker/
lib.rs

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