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;