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;