This is a guest post by Arijit Dey, introducing a cross-platform paging crate he wrote, which will allow Pijul to work equally on all platforms.
Hello everyone. Before I continue on with this blog post, I really want to thank Pierre-Étienne Meunier for giving me this opportunity to write a blog post on their website
In this blog post, I want to talk about Minus, which is a cross-platform, asynchronous terminal paging library written in Rust. You can check it out here
I started writing minus because of my work on Pijul. It all started with a discussion where I pointed out that we should use a pager for easier viewing of pijul logs/diffs/credits. After getting encouragement from a few people, I started working on a implementation, unfortunately all of the existing Rust paging crates were kind a unsatisfactory to me. The first option that I looked into was pager. It was a popular library and had good documentation. I had to write only a few lines to start using it. And this is when I got my first dissatisfaction:
less
or more
to be availableCtrl+C
and then q
to quit (this has been fixed in Pijul by exiting explicitly with std::env::exit(0)
).Two days later, I discovered bigger problems:
less
or more
along with the binaryBetween these two days, I had submitted another implementation using the moins crate. It did solve the first two issues, but introduced another issue: indeed, there can be a long hang-up when running log/diff/credit on a really large repository, and due to the nature of the moins
crate, the data must be supplied entirely before anything is rendered, which means we cannot atomically update the result.
Although the first implementation using the pager
crate was merged into the main Pijul codebase, I wasn’t really happy with the implementation. So I wrote a small post on the Pijul Discourse about writing a new paging library for Pijul.
The first thing before starting any project is to understand what your project’s primary goals are. What are the most important things that it must solve? For minus, these are the primary goals:
Some dependencies just immediately solve most problems:
Without going into all the hit and trials that I had to do, I am going straight to the current implementation
For different applications, different features and functions are available
tokio_lib
feature and appropriate initializer function is tokio_updating
async_std_lib
feature and appropriate initializer function is async_std_updating
static_output
function and appropriate function is page_all
The application has to initialize an Arc<Mutex<String>>
and give a clone of it to the respective dynamic initializer function. If you are paging static output, page_all
takes a String
rather than an Arc
.
Minus then initializes itself, switching to alternate screen of the terminal, enabling raw mode of the terminal and hiding the cursor. Then it checks whether there are more lines in the terminal, then the number of lines in the output. In case of dynamic paging, it keeps continuously updating with new information. In static paging, minus prints the entire output and quits
If there are more lines of output than number of rows, it only displays a range of output, between upper_mark
and lower_mark
. upper_mark
is what we keep a track of, it is simply a pointer of scrolling: whenever the user presses the down arrow key, we increment upper_mark
by 1 and when they press the arrow up key, we decrement upper_mark
. lower_mark
is a calculated value, using this formula
upper_mark + number of lines in terminal = lower_mark
While doing all this, we also check for events, up arrow, down arrow, q
or Ctrl+C
resets all changes to terminal and quits. Resize events sent by Crossterm update the number of lines in terminal.
So that’s a quick rundown of the inner workings of minus and how it works with other top-level programs.
There have been some questions about what others can expect from minus. Let me clear them here:
As far as I am concerned, I currently don’t have any plans to do so. But I really encourage the community to make something out of it. There are some things which are application specific, like piping stdout of another program into stdin of the application.
I am ready to work with these guys to maybe integrate minus into something like bat/ripgrep to provide better output. This would bring lots of improvements to minus as well.
Minus is the Latin translation of less
Absolutely, just make sure that every line starts and resets the formatting. Basically your line should be a concatenation of regular text and sequences like:
(Whatever formatting ANSI sequence)(regular text)(Reset sequence)
If you’re using ansi_term, use something like this
// Assuming formatted_line is the final input to the pager
let mut formatted_line = String::new();
let line = String::from("Hello World");
formatted_line.push_str(&ansi_term::Colour::Blue.paint(line).to_string());
minus::page_all(formatted_line);
So, that’s some quick introduction and workings of minus. Pijul developers are currently working to integrate it in Pijul. I am also currently to make minus more efficient and bug free. I hope this blog post helps.