Crate minus

source · []
Expand description

minus is an asynchronous terminal paging library written in Rust.

What is a Pager?

A pager is a program that lets you view and scroll through large amounts of text using a keyboard in a TTY where no mouse support is available.

Nowadays most people use a graphical terminals where mouse support is present but they aren’t as reliable as a pager. For example they may not support proper text searching or line numbering, plus quick navigation using keyboard is pretty much non-existent.

Examples of some popular pager include more and its successor less.

The problem with traditional pagers

First, traditional pagers like more or less weren’t made for integrating into other applications. They were meant to be standalone binaries that are executed directly by the users.

Applications leveraged these pagers by calling them as external programs and passing the data through the standard input. This method worked for Unix and other Unix-like OSs like Linux and MacOS because they already came with any of these pagers installed But it wasn’t this eas y on Windows, it required shipping the pager binary along with the applications. Since these p rograms were originally designed for Unix and Unix-like OSs, distributing these binaries meant shipping an entire environment like MinGW or Cygwin so that these can run properly on Windows.

Recently, some libraries have emerged to solve this issue. They are compiled along with your application and give you a single to distribute. The problem with this is most of them require you to feed the entire data to the pager before the pager can run, this meant that there will be no output on the terminal until the entire data isn’t loaded by the application and passed on to the pager.

These could cause long delays before output if the data comes from a very large file or is being downloaded from the internet.

Enter minus

As above described, minus is an asynchronous terminal paging library for Rust. It allows not just data but also configuration to be fed into itself while it is running.

minus achieves this by using Rust’s amazing concurrency support and no data race conditions which are guaranteed by Rust

With minus, you can initialize a pager with any async runtime of your choice or even no runtime, if you want just native threads.

Usage

Add minus as a dependency in your Cargo.toml file and enable features as you like.

  • If you only want a pager to display static data, enable the static_output feature
  • If you want a pager to display dynamic data and be configurable at runtime, enable the async_output feature
  • If you want search support inside the pager, you need to enable the search feature
[dependencies.minus]
version = "^5.0"
features = [
   "async_output",
   "search"
]

Examples

All example are available in the examples directory and you can run them using cargo.

tokio

use minus::{dynamic_paging, MinusError, Pager};
use std::time::Duration;
use std::fmt::Write;
use tokio::{join, task::spawn_blocking, time::sleep};

#[tokio::main]
async fn main() -> Result<(), MinusError> {
    // Initialize the pager
    let mut pager = Pager::new();
    // Asynchronously send data to the pager
    let increment = async {
        let mut pager = pager.clone();
        for i in 0..=100_u32 {
            writeln!(pager, "{}", i);
            sleep(Duration::from_millis(100)).await;
        }
        Result::<_, MinusError>::Ok(())
    };
    // spawn_blocking(dynamic_paging(...)) creates a separate thread managed by the tokio
    // runtime and runs the async_paging inside it
    let pager = pager.clone();
    let (res1, res2) = join!(spawn_blocking(move || dynamic_paging(pager)), increment);
    // .unwrap() unwraps any error while creating the tokio task
    //  The ? mark unpacks any error that might have occured while the
    // pager is running
    res1.unwrap()?;
    res2?;
    Ok(())
}

async-std:

use async_std::task::{sleep, spawn};
use futures_lite::future;
use minus::{dynamic_paging, MinusError, Pager};
use std::time::Duration;

#[async_std::main]
async fn main() -> Result<(), MinusError> {
    let output = Pager::new();

    let increment = async {
        for i in 0..=100_u32 {
            output.push_str(&format!("{}\n", i))?;
            sleep(Duration::from_millis(100)).await;
        }
        Result::<_, MinusError>::Ok(())
    };

    let output = output.clone();
    let (res1, res2) = future::zip(spawn(async move { dynamic_paging(output) }), increment).await;
    res1?;
    res2?;
    Ok(())
}

Static output:

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

fn main() -> Result<(), MinusError> {
    // Initialize a default static configuration
    let mut output = Pager::new();
    // 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.

Standard actions

Here is the list of default key/mouse actions handled by minus. End-applications can change these bindings to better suit their needs.

ActionDescription
Ctrl+C/qQuit the pager
Arrow Up/kScroll up by one line
Arrow Down/jScroll down by one line
Page UpScroll up by entire page
Page DownScroll down by entire page
EnterScroll down by one line or clear prompt messages
SpaceScroll down by one page
Ctrl+U/uScroll up by half a screen
Ctrl+D/dScroll down by half a screen
gGo to the very top of the output
GGo to the very bottom of the output
Mouse scroll UpScroll up by 5 lines
Mouse scroll DownScroll down by 5 lines
Ctrl+LToggle line numbers if not forced enabled/disabled
/Start forward search
?Start backward search
EscCancel search input
nGo to the next search match
pGo to the next previous match

Re-exports

pub use error::MinusError;

Modules

Provides error types that are used in various places

Provides the InputClassifier trait, which can be used to customize the default keybindings of minus

Structs

A pager acts as a middleman for communication between the main application and the user with the core functions of minus

Holds all information and configuration about the pager during its un time.

Enums

Behaviour that happens when the pager is exitted

Enum indicating whether to display the line numbers or not.

Defines modes in which the search can run

Functions

Starts a asynchronously running pager

page_allstatic_output

Display static information to the screen

Type Definitions

A convenient type for Vec<Box<dyn FnMut() + Send + Sync + 'static>>