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}