minus/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2// When no feature is active this crate is unusable but contains lots of
3// unused imports and dead code. To avoid useless warnings about this they
4// are allowed when no feature is active.
5#![cfg_attr(
6    not(any(feature = "dynamic_output", feature = "static_output")),
7    allow(unused_imports),
8    allow(dead_code)
9)]
10#![deny(clippy::all)]
11#![warn(clippy::pedantic)]
12#![warn(clippy::nursery)]
13#![allow(clippy::doc_markdown)]
14#![cfg_attr(doctest, doc = include_str!("../README.md"))]
15
16//! `minus`: A library for asynchronous terminal [paging], written in Rust.
17//!
18//! If you want to learn about its motivation and features, please take a look into it's [README].
19//!
20//! # Overview
21//! When getting started with minus, the two most important concepts to get familier with are:
22//! * The [Pager] type: which acts as a bridge between your application and minus. It is used
23//! to pass data and configure minus before and after starting the pager.
24//! * Initialization functions: This includes the [dynamic_paging] and [page_all] functions which
25//! take a [Pager] as argument. They are responsible for generating the initial state and starting
26//! the pager.
27//!
28//! See the docs for the respective items to learn more on its usage.
29//!
30//! # Examples
31//!
32//! ## Threads
33//!
34//! ```rust,no_run
35//! use minus::{dynamic_paging, MinusError, Pager};
36//! use std::{
37//!     fmt::Write,
38//!     thread::{spawn, sleep},
39//!     time::Duration
40//! };
41//!
42//! fn main() -> Result<(), MinusError> {
43//!     // Initialize the pager
44//!     let mut pager = Pager::new();
45//!     // Run the pager in a separate thread
46//!     let pager2 = pager.clone();
47//!     let pager_thread = spawn(move || dynamic_paging(pager2));
48//!
49//!     for i in 0..=100_u32 {
50//!         writeln!(pager, "{}", i);
51//!         sleep(Duration::from_millis(100));
52//!     }
53//!     pager_thread.join().unwrap()?;
54//!     Ok(())
55//! }
56//! ```
57//!
58//! ## tokio
59//!
60//! ```rust,no_run
61//! use minus::{dynamic_paging, MinusError, Pager};
62//! use std::time::Duration;
63//! use std::fmt::Write;
64//! use tokio::{join, task::spawn_blocking, time::sleep};
65//!
66//! #[tokio::main]
67//! async fn main() -> Result<(), MinusError> {
68//!     // Initialize the pager
69//!     let mut pager = Pager::new();
70//!     // Asynchronously send data to the pager
71//!     let increment = async {
72//!         let mut pager = pager.clone();
73//!         for i in 0..=100_u32 {
74//!             writeln!(pager, "{}", i);
75//!             sleep(Duration::from_millis(100)).await;
76//!         }
77//!         Result::<_, MinusError>::Ok(())
78//!     };
79//!     // spawn_blocking(dynamic_paging(...)) creates a separate thread managed by the tokio
80//!     // runtime and runs the async_paging inside it
81//!     let pager = pager.clone();
82//!     let (res1, res2) = join!(spawn_blocking(move || dynamic_paging(pager)), increment);
83//!     // .unwrap() unwraps any error while creating the tokio task
84//!     //  The ? mark unpacks any error that might have occurred while the
85//!     // pager is running
86//!     res1.unwrap()?;
87//!     res2?;
88//!     Ok(())
89//! }
90//! ```
91//!
92//! ## Static output
93//! ```rust,no_run
94//! use std::fmt::Write;
95//! use minus::{MinusError, Pager, page_all};
96//!
97//! fn main() -> Result<(), MinusError> {
98//!     // Initialize a default static configuration
99//!     let mut output = Pager::new();
100//!     // Push numbers blockingly
101//!     for i in 0..=30 {
102//!         writeln!(output, "{}", i)?;
103//!     }
104//!     // Run the pager
105//!     minus::page_all(output)?;
106//!     // Return Ok result
107//!     Ok(())
108//! }
109//! ```
110//!
111//! **Note:**
112//! In static mode, `minus` doesn't start the pager and just prints the content if the current terminal size can
113//! display all lines. You can of course change this behaviour.
114//!
115//! ## Default keybindings
116//!
117//! Here is the list of default key/mouse actions handled by `minus`.
118//!
119//! **A `[n] key` means that you can precede the key by an integer**.
120//!
121//! | Action              | Description                                                                  |
122//! |---------------------|------------------------------------------------------------------------------|
123//! | Ctrl+C/q            | Quit the pager                                                               |
124//! | \[n\] Arrow Up/k    | Scroll up by n number of line(s). If n is omitted, scroll up by 1 line       |
125//! | \[n\] Arrow Down/j  | Scroll down by n number of line(s). If n is omitted, scroll down by 1 line   |
126//! | Ctrl+h              | Turn off line wrapping and allow horizontal scrolling                        |
127//! | \[n\] Arrow left/h  | Scroll left by n number of line(s). If n is omitted, scroll up by 1 line     |
128//! | \[n\] Arrow right/l | Scroll right by n number of line(s). If n is omitted, scroll down by 1 line  |
129//! | Page Up             | Scroll up by entire page                                                     |
130//! | Page Down           | Scroll down by entire page                                                   |
131//! | \[n\] Enter         | Scroll down by n number of line(s).                                          |
132//! | Space               | Scroll down by one page                                                      |
133//! | Ctrl+U/u            | Scroll up by half a screen                                                   |
134//! | Ctrl+D/d            | Scroll down by half a screen                                                 |
135//! | g                   | Go to the very top of the output                                             |
136//! | \[n\] G             | Go to the very bottom of the output. If n is present, goes to that line      |
137//! | Mouse scroll Up     | Scroll up by 5 lines                                                         |
138//! | Mouse scroll Down   | Scroll down by 5 lines                                                       |
139//! | Ctrl+L              | Toggle line numbers if not forced enabled/disabled                           |
140//! | Ctrl+f              | Toggle [follow-mode]                                                         |
141//! | /                   | Start forward search                                                         |
142//! | ?                   | Start backward search                                                        |
143//! | Esc                 | Cancel search input                                                          |
144//! | n                   | Go to the next search match                                                  |
145//! | p                   | Go to the next previous match                                                |
146//!
147//! End-applications are free to change these bindings to better suit their needs. See docs for
148//! [Pager::set_input_classifier] function and [input] module.
149//!
150//! ## Key Bindings Available at Search Prompt
151//!
152//! | Key Bindings      | Description                                         |
153//! |-------------------|-----------------------------------------------------|
154//! | Esc               | Cancel the search                                   |
155//! | Enter             | Confirm the search query                            |
156//! | Backspace         | Remove the character before the cursor              |
157//! | Delete            | Remove the character under the cursor               |
158//! | Arrow Left        | Move cursor towards left                            |
159//! | Arrow right       | Move cursor towards right                           |
160//! | Ctrl+Arrow left   | Move cursor towards left word by word               |
161//! | Ctrl+Arrow right  | Move cursor towards right word by word              |
162//! | Home              | Move cursor at the beginning pf search query        |
163//! | End               | Move cursor at the end pf search query              |
164//!
165//! Currently these cannot be changed by applications but this may be supported in the future.
166//!
167//! [`tokio`]: https://docs.rs/tokio
168//! [`async-std`]: https://docs.rs/async-std
169//! [`Threads`]: std::thread
170//! [follow-mode]: struct.Pager.html#method.follow_output
171//! [paging]: https://en.wikipedia.org/wiki/Terminal_pager
172//! [README]: https://github.com/arijit79/minus#motivation
173#[cfg(feature = "dynamic_output")]
174mod dynamic_pager;
175pub mod error;
176pub mod input;
177#[path = "core/mod.rs"]
178mod minus_core;
179mod pager;
180pub mod screen;
181#[cfg(feature = "search")]
182#[cfg_attr(docsrs, doc(cfg(feature = "search")))]
183pub mod search;
184pub mod state;
185#[cfg(feature = "static_output")]
186mod static_pager;
187
188#[cfg(feature = "dynamic_output")]
189pub use dynamic_pager::dynamic_paging;
190#[cfg(feature = "static_output")]
191pub use static_pager::page_all;
192
193pub use minus_core::RunMode;
194#[cfg(feature = "search")]
195pub use search::SearchMode;
196
197pub use error::MinusError;
198pub use pager::Pager;
199pub use state::PagerState;
200
201/// A convenient type for `Vec<Box<dyn FnMut() + Send + Sync + 'static>>`
202pub type ExitCallbacks = Vec<Box<dyn FnMut() + Send + Sync + 'static>>;
203
204/// Result type returned by most minus's functions
205type Result<T = (), E = MinusError> = std::result::Result<T, E>;
206
207/// Behaviour that happens when the pager is exited
208#[derive(PartialEq, Clone, Debug, Eq)]
209pub enum ExitStrategy {
210    /// Kill the entire application immediately.
211    ///
212    /// This is the preferred option if paging is the last thing you do. For example,
213    /// the last thing you do in your program is reading from a file or a database and
214    /// paging it concurrently
215    ///
216    /// **This is the default strategy.**
217    ProcessQuit,
218    /// Kill the pager only.
219    ///
220    /// This is the preferred option if you want to do more stuff after exiting the pager. For example,
221    /// if you've file system locks or you want to close database connectiions after
222    /// the pager has done i's job, you probably want to go for this option
223    PagerQuit,
224}
225
226/// Enum indicating whether to display the line numbers or not.
227///
228/// Note that displaying line numbers may be less performant than not doing it.
229/// `minus` tries to do as quickly as possible but the numbers and padding
230/// still have to be computed.
231///
232/// This implements [`Not`](std::ops::Not) to allow turning on/off line numbers
233/// when they where not locked in by the binary displaying the text.
234#[derive(Debug, PartialEq, Eq, Copy, Clone)]
235pub enum LineNumbers {
236    /// Enable line numbers permanently, cannot be turned off by user.
237    AlwaysOn,
238    /// Line numbers should be turned on, although users can turn it off
239    /// (i.e, set it to `Disabled`).
240    Enabled,
241    /// Line numbers should be turned off, although users can turn it on
242    /// (i.e, set it to `Enabled`).
243    Disabled,
244    /// Disable line numbers permanently, cannot be turned on by user.
245    AlwaysOff,
246}
247
248impl LineNumbers {
249    const EXTRA_PADDING: usize = 5;
250
251    /// Returns `true` if `self` can be inverted (i.e, `!self != self`), see
252    /// the documentation for the variants to know if they are invertible or
253    /// not.
254    #[allow(dead_code)]
255    const fn is_invertible(self) -> bool {
256        matches!(self, Self::Enabled | Self::Disabled)
257    }
258
259    const fn is_on(self) -> bool {
260        matches!(self, Self::Enabled | Self::AlwaysOn)
261    }
262}
263
264impl std::ops::Not for LineNumbers {
265    type Output = Self;
266
267    fn not(self) -> Self::Output {
268        use LineNumbers::{Disabled, Enabled};
269
270        match self {
271            Enabled => Disabled,
272            Disabled => Enabled,
273            ln => ln,
274        }
275    }
276}
277
278#[cfg(test)]
279mod tests;