33 releases (stable)

new 4.0.3 Nov 28, 2021
4.0.1 Aug 18, 2021
4.0.0 Jul 16, 2021
3.4.0 May 26, 2021
1.0.3 Dec 5, 2020

#35 in Asynchronous

Download history 311/week @ 2021-08-12 169/week @ 2021-08-19 74/week @ 2021-08-26 48/week @ 2021-09-02 124/week @ 2021-09-09 117/week @ 2021-09-16 84/week @ 2021-09-23 119/week @ 2021-09-30 45/week @ 2021-10-07 91/week @ 2021-10-14 63/week @ 2021-10-21 92/week @ 2021-10-28 433/week @ 2021-11-04 23/week @ 2021-11-11 47/week @ 2021-11-18 80/week @ 2021-11-25

639 downloads per month
Used in 5 crates

MIT/Apache

170KB
2K SLoC

minus

crates.io ci docs.rs chat Crates.io

A fast, asynchronous terminal paging library for Rust. minus provides high level functions to easily embed a pager for any terminal application. Due to the asynchronous nature of minus, the pager's data and configuration can be updated at any time.

Demo.png

minus supports both tokio as well as async-std runtimes. What's more, if you only want to use minus for serving static output, you can simply opt out of these dynamic features, see the Usage section below.

Why this crate ?

minus was started by me for my work on pijul. I was unsatisfied with the existing options like pager and moins.

  • pager:

    • Only provides functions to join the standard output of the current program to the standard input of external pager like more or less.
    • Due to this, to work within Windows, the external pagers need to be packaged along with the executable.
  • moins:

    • The output could only be defined once and for all. It is not asynchronous and does not support updating.

Usage

  • Using tokio for your application ? Use the tokio_lib feature.
  • Using async-std for your application ? Use the async_std_lib feature.
  • Using only static information ? Use the static_output feature.

In your Cargo.toml file:

[dependencies.minus]
version = "^4.0"
# For tokio
features = ["tokio_lib"]

# For async_std
features = ["async_std_lib"]

# For static output
features = ["static_output"]

# If you want search capablities
features = ["search"]

Examples

All examples are available in the examples directory and you can run them using cargo. Remember to set the correct feature for the targeted example (e.g.: cargo run --example=dyn_tokio --features=tokio_lib).

Using tokio:

use futures::join;
use tokio::time::sleep;
use minus::{Pager, async_std_updating};
use std::fmt::Write;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize a default dynamic configuration
    let pager = minus::Pager::new().unwrap().finish();

    // Asynchronously push numbers to the output
    let increment = async {
        for i in 0..=30_u32 {
            let mut guard = pager.lock().await;
            writeln!(guard, "{}", i)?;
            // Also you can use this syntax
            // guard.push_str(&format("{}\n", i));
            drop(guard);
            sleep(Duration::from_millis(100)).await;
        }
        // Dynamic paging should hint the pager that it's stream of data has
        // ended
        let mut guard.lock().await;
        guard.end_data_stream();
        // Return an Ok result
        Result::<_, std::fmt::Error>::Ok(())
    };

    // Join the futures
    let (res1, res2) = join!(
        minus::tokio_updating(pager.clone()),
        increment
    );
    // Check for errors
    res1?;
    res2?;
    // Return Ok result
    Ok(())
}

Using async-std:

use async_std::task::sleep;
use futures::join;
use minus::{Pager, async_std_updating};
use std::fmt::Write;
use std::time::Duration;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize a default dynamic configuration
    let pager = minus::Pager::new().unwrap().finish();

    // Asynchronously push numbers to the output
    let increment = async {
        for i in 0..=30_u32 {
            let mut guard = pager.lock().await;
            writeln!(guard, "{}", i)?;
            // Also you can use this syntax
            // guard.push_str(&format("{}\n", i));
            drop(guard);
            sleep(Duration::from_millis(100)).await;
        }
        // Dynamic paging should hint the pager that it's stream of data has
        // ended
        let mut guard.lock().await;
        guard.end_data_stream();
        // Return an Ok result
        Result::<_, std::fmt::Error>::Ok(())
    };
    // Join the futures
    let (res1, res2) = join!(
        minus::async_std_updating(guard.clone()), increment);

    // Check for errors
    res1?;
    res2?;
    // Return Ok result
    Ok(())
}

Some static output:

use std::fmt::Write;
use minus::{Pager, page_all};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize a default static configuration
    let mut output = Pager::new().unwrap();
    // Push numbers blockingly
    for i in 0..=30 {
        writeln!(output, "{}", i)?;
    }
    // Run the pager
    minus::page_all(output)?;
    // Return Ok result
    Ok(())
}

If there are more rows in the terminal than the number of lines in the given data, minus will simply print the data and quit. This only works in static paging since asynchronous paging could still receive more data that makes it pass the limit.

End user help

Here is the list of default key bindings.

Action Description
Ctrl+C/q Quit the pager
Arrow Up/k Scroll up by one line
Arrow Down/j Scroll down by one line
Page Up Scroll up by entire page
Page Down Scroll down by entire page
Enter Scroll down by one line or clear prompt messages
Space Scroll down by one page
Ctrl+U/u Scroll up by half a screen
Ctrl+D/d Scroll down by half a screen
g Go to the very top of the output
G Go to the very bottom of the output
Mouse scroll Up Scroll up by 5 lines
Mouse scroll Down Scroll down by 5 lines
Ctrl+L Toggle line numbers if not forced enabled/disabled
/ Start forward search
? Start backward search
Esc Cancel search input
n Go to the next search match
p Go to the next previous match

Applications can customize these keybindings to better suite there needs

License

Unless explicitly stated, all works to minus are dual licensed under the MIT License and Apache License 2.0

Contributing

Issues and pull requests are more than welcome. See CONTRIBUTING.md on how to contribute to minus.

Thanks

Thank you to everyone here for giving there time and contribution to minus

  • @rezural
  • @poliorcetics
  • @danieleades
  • @mark-a
  • @mkatychev
  • @tomstoneham
  • @Hardy7cc
  • @tomstoneham
  • @iandwelker

Get in touch

We are open to discussion and thoughts om improving minus. Join us at Zulip

Dependencies

~1.2–3MB
~59K SLoC