Skip to main content

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