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}