19 releases (7 breaking)

new 0.8.0 Jan 14, 2025
0.7.0 Jan 13, 2025
0.6.4 Dec 16, 2024
0.5.0 Nov 7, 2024
0.1.0 Aug 20, 2024

#101 in Command-line interface

Download history 15/week @ 2024-09-25 7/week @ 2024-10-02 12/week @ 2024-10-09 2/week @ 2024-10-16 1/week @ 2024-10-30 125/week @ 2024-11-06 10/week @ 2024-11-13 22/week @ 2024-11-20 106/week @ 2024-11-27 182/week @ 2024-12-04 454/week @ 2024-12-11 31/week @ 2024-12-18 6/week @ 2024-12-25 575/week @ 2025-01-08

648 downloads per month
Used in 2 crates

MIT/Apache

220KB
4K SLoC

Current crates.io release Documentation

nucleo-picker

A native Rust library which enables you to incorporate a highly performant and Unicode-aware fuzzy picker directly in your own terminal application.

This library provides a TUI for the nucleo crate with an interface similar to the fzf command-line tool.

  • For implementation examples, jump to the fzf example or see the examples directory.
  • For documentation of interactive usage of the picker, see the USAGE.md file.
  • For a list of recent changes, see the CHANGELOG.md file.

Elevator pitch

Why use this library instead of a general-purpose fuzzy-finder such as fzf or a lower level library such as nucleo?

  1. Much tighter integration between your data source and your application. Instead of reading from a SQLite database with sqlite3 and then parsing raw text, read directly into in-memory data structures with rusqlite and render the in-memory objects in the picker.
  2. Skip the subprocess overhead and improve startup time. Instead of starting up a subprocess to call fzf, have the picker integrated directly into your binary.
  3. Distinguish items from their matcher representation. Instead of writing your data structure to a string, passing it to fzf, and then parsing the resulting match string back into your data structure, directly obtain the original data structure when matching is complete.
  4. Don't spend time debugging terminal rendering edge cases. Out-of-the-box, nucleo-picker handles terminal rendering subtleties such as multiline rendering, double-width Unicode, automatic overflow scrollthrough, and grapheme-aware query input so you don't have to.
  5. Handle complex use cases using events. nucleo-picker exposes a fully-featured event system which can be used to drive the picker. This lets you customize keybindings, support interactive restarts, and much more by implementing the EventSource trait. Simplified versions of such features are available in fzf but essentially require manual configuration via an embedded DSL.

Features

  • Highly optimized matching.
  • Robust rendering:
    • Full Unicode handling with Unicode text segmentation and Unicode width.
    • Match highlighting with automatic scroll-through.
    • Correctly render multi-line or overflowed items, with standard and reversed item order.
    • Responsive interface with batched keyboard input.
  • Ergonomic API:
    • Fully concurrent lock- and wait-free streaming of input items.
    • Generic Picker for any type T which is Send + Sync + 'static.
    • Customizable rendering of crate-local and foreign types with the Render trait.
  • Fully configurable event system:
    • Easily customizable keybindings.
    • Run the picker concurrently with your application using a fully-featured event system, with optional support for complex features such as interactive restarting.
    • Optional and flexible error propagation generics so your application errors can interface cleanly with the picker.

Example

Implement a heavily simplified fzf clone in 25 lines of code. Try it out with:

cargo build --release --example fzf
cat myfile.txt | ./target/release/examples/fzf

The code to create the binary:

use std::{
    io::{self, IsTerminal},
    process::exit,
    thread::spawn,
};

use nucleo_picker::{render::StrRenderer, Picker};

fn main() -> io::Result<()> {
    let mut picker = Picker::new(StrRenderer);

    let injector = picker.injector();
    spawn(move || {
        let stdin = io::stdin();
        if !stdin.is_terminal() {
            for line in stdin.lines() {
                // silently drop IO errors!
                if let Ok(s) = line {
                    injector.push(s);
                }
            }
        }
    });

    match picker.pick()? {
        Some(it) => println!("{it}"),
        None => exit(1),
    }
    Ok(())
}

This crate mainly exists as a result of the author's annoyance with pretty much every fuzzy picker TUI in the rust ecosystem. As far as I am aware, the fully-exposed event system is unique to this crate. Beyond this, here is a brief comparison:

  • skim's Arc<dyn SkimItem> is inconvenient for a variety of reasons. skim also has a large number of dependencies and is designed more as a binary than a library.
  • fuzzypicker is based on skim and inherits skim's problems.
  • nucleo-ui only has a blocking API and only supports matching on String. It also seems to be un-maintained.
  • fuzzy-select only has a blocking API.
  • dialoguer FuzzySelect only has a blocking API and only supports matching on String. The terminal handling also has a few strange bugs.

Disclaimer

There are a currently a few known problems which have not been addressed (see the issues page on GitHub for a list). Issues and contributions are welcome!

Dependencies

~6–15MB
~212K SLoC