1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#![cfg_attr(docsrs, feature(doc_cfg))]
// When no feature is active this crate is unusable but contains lots of
// unused imports and dead code. To avoid useless warnings about this they
// are allowed when no feature is active.
#![cfg_attr(
    not(any(feature = "dynamic_output", feature = "static_output")),
    allow(unused_imports),
    allow(dead_code)
)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![allow(clippy::doc_markdown)]
#![cfg_attr(doctest, doc = include_str!("../README.md"))]

//! `minus`: A library for asynchronous terminal [paging], written in Rust.
//!
//! If you want to learn about its motivation and features, please take a look into it's [README].
//!
//! # Overview
//! When getting started with minus, the two most important concepts to get familier with are:
//! * The [Pager] type: which acts as a bridge between your application and minus. It is used
//! to pass data and configure minus before and after starting the pager.
//! * Initialization functions: This includes the [dynamic_paging] and [page_all] functions which
//! take a [Pager] as argument. They are responsible for generating the initial state and starting
//! the pager.
//!
//! See the docs for the respective items to learn more on its usage.
//!
//! # Examples
//!
//! ## Threads
//!
//! ```rust,no_run
//! use minus::{dynamic_paging, MinusError, Pager};
//! use std::{
//!     fmt::Write,
//!     thread::{spawn, sleep},
//!     time::Duration
//! };
//!
//! fn main() -> Result<(), MinusError> {
//!     // Initialize the pager
//!     let mut pager = Pager::new();
//!     // Run the pager in a separate thread
//!     let pager2 = pager.clone();
//!     let pager_thread = spawn(move || dynamic_paging(pager2));
//!
//!     for i in 0..=100_u32 {
//!         writeln!(pager, "{}", i);
//!         sleep(Duration::from_millis(100));
//!     }
//!     pager_thread.join().unwrap()?;
//!     Ok(())
//! }
//! ```
//!
//! ## tokio
//!
//! ```rust,no_run
//! use minus::{dynamic_paging, MinusError, Pager};
//! use std::time::Duration;
//! use std::fmt::Write;
//! use tokio::{join, task::spawn_blocking, time::sleep};
//!
//! #[tokio::main]
//! async fn main() -> Result<(), MinusError> {
//!     // Initialize the pager
//!     let mut pager = Pager::new();
//!     // Asynchronously send data to the pager
//!     let increment = async {
//!         let mut pager = pager.clone();
//!         for i in 0..=100_u32 {
//!             writeln!(pager, "{}", i);
//!             sleep(Duration::from_millis(100)).await;
//!         }
//!         Result::<_, MinusError>::Ok(())
//!     };
//!     // spawn_blocking(dynamic_paging(...)) creates a separate thread managed by the tokio
//!     // runtime and runs the async_paging inside it
//!     let pager = pager.clone();
//!     let (res1, res2) = join!(spawn_blocking(move || dynamic_paging(pager)), increment);
//!     // .unwrap() unwraps any error while creating the tokio task
//!     //  The ? mark unpacks any error that might have occurred while the
//!     // pager is running
//!     res1.unwrap()?;
//!     res2?;
//!     Ok(())
//! }
//! ```
//!
//! ## Static output
//! ```rust,no_run
//! use std::fmt::Write;
//! use minus::{MinusError, Pager, page_all};
//!
//! fn main() -> Result<(), MinusError> {
//!     // Initialize a default static configuration
//!     let mut output = Pager::new();
//!     // Push numbers blockingly
//!     for i in 0..=30 {
//!         writeln!(output, "{}", i)?;
//!     }
//!     // Run the pager
//!     minus::page_all(output)?;
//!     // Return Ok result
//!     Ok(())
//! }
//! ```
//!
//! **Note:**
//! In static mode, `minus` doesn't start the pager and just prints the content if the current terminal size can
//! display all lines. You can of course change this behaviour.
//!
//! ## Default keybindings
//!
//! Here is the list of default key/mouse actions handled by `minus`.
//!
//! **A `[n] key` means that you can precede the key by an integer**.
//!
//! | Action              | Description                                                                  |
//! |---------------------|------------------------------------------------------------------------------|
//! | Ctrl+C/q            | Quit the pager                                                               |
//! | \[n\] Arrow Up/k    | Scroll up by n number of line(s). If n is omitted, scroll up by 1 line       |
//! | \[n\] Arrow Down/j  | Scroll down by n number of line(s). If n is omitted, scroll down by 1 line   |
//! | Ctrl+h              | Turn off line wrapping and allow horizontal scrolling                        |
//! | \[n\] Arrow left/h  | Scroll left by n number of line(s). If n is omitted, scroll up by 1 line     |
//! | \[n\] Arrow right/l | Scroll right by n number of line(s). If n is omitted, scroll down by 1 line  |
//! | Page Up             | Scroll up by entire page                                                     |
//! | Page Down           | Scroll down by entire page                                                   |
//! | \[n\] Enter         | Scroll down by n number of line(s).                                          |
//! | Space               | Scroll down by one page                                                      |
//! | Ctrl+U/u            | Scroll up by half a screen                                                   |
//! | Ctrl+D/d            | Scroll down by half a screen                                                 |
//! | g                   | Go to the very top of the output                                             |
//! | \[n\] G             | Go to the very bottom of the output. If n is present, goes to that line      |
//! | Mouse scroll Up     | Scroll up by 5 lines                                                         |
//! | Mouse scroll Down   | Scroll down by 5 lines                                                       |
//! | Ctrl+L              | Toggle line numbers if not forced enabled/disabled                           |
//! | Ctrl+f              | Toggle [follow-mode]                                                         |
//! | /                   | Start forward search                                                         |
//! | ?                   | Start backward search                                                        |
//! | Esc                 | Cancel search input                                                          |
//! | n                   | Go to the next search match                                                  |
//! | p                   | Go to the next previous match                                                |
//!
//! End-applications are free to change these bindings to better suit their needs. See docs for
//! [Pager::set_input_classifier] function and [input] module.
//!
//! ## Key Bindings Available at Search Prompt
//!
//! | Key Bindings      | Description                                         |
//! |-------------------|-----------------------------------------------------|
//! | Esc               | Cancel the search                                   |
//! | Enter             | Confirm the search query                            |
//! | Backspace         | Remove the character before the cursor              |
//! | Delete            | Remove the character under the cursor               |
//! | Arrow Left        | Move cursor towards left                            |
//! | Arrow right       | Move cursor towards right                           |
//! | Ctrl+Arrow left   | Move cursor towards left word by word               |
//! | Ctrl+Arrow right  | Move cursor towards right word by word              |
//! | Home              | Move cursor at the beginning pf search query        |
//! | End               | Move cursor at the end pf search query              |
//!
//! Currently these cannot be changed by applications but this may be supported in the future.
//!
//! [`tokio`]: https://docs.rs/tokio
//! [`async-std`]: https://docs.rs/async-std
//! [`Threads`]: std::thread
//! [follow-mode]: struct.Pager.html#method.follow_output
//! [paging]: https://en.wikipedia.org/wiki/Terminal_pager
//! [README]: https://github.com/arijit79/minus#motivation
#[cfg(feature = "dynamic_output")]
mod dynamic_pager;
pub mod error;
pub mod input;
#[path = "core/mod.rs"]
mod minus_core;
mod pager;
pub mod screen;
#[cfg(feature = "search")]
#[cfg_attr(docsrs, doc(cfg(feature = "search")))]
pub mod search;
pub mod state;
#[cfg(feature = "static_output")]
mod static_pager;

#[cfg(feature = "dynamic_output")]
pub use dynamic_pager::dynamic_paging;
#[cfg(feature = "static_output")]
pub use static_pager::page_all;

pub use minus_core::RunMode;
#[cfg(feature = "search")]
pub use search::SearchMode;

pub use error::MinusError;
pub use pager::Pager;
pub use state::PagerState;

/// A convenient type for `Vec<Box<dyn FnMut() + Send + Sync + 'static>>`
pub type ExitCallbacks = Vec<Box<dyn FnMut() + Send + Sync + 'static>>;

/// Result type returned by most minus's functions
type Result<T = (), E = MinusError> = std::result::Result<T, E>;

/// Behaviour that happens when the pager is exited
#[derive(PartialEq, Clone, Debug, Eq)]
pub enum ExitStrategy {
    /// Kill the entire application immediately.
    ///
    /// This is the preferred option if paging is the last thing you do. For example,
    /// the last thing you do in your program is reading from a file or a database and
    /// paging it concurrently
    ///
    /// **This is the default strategy.**
    ProcessQuit,
    /// Kill the pager only.
    ///
    /// This is the preferred option if you want to do more stuff after exiting the pager. For example,
    /// if you've file system locks or you want to close database connectiions after
    /// the pager has done i's job, you probably want to go for this option
    PagerQuit,
}

/// Enum indicating whether to display the line numbers or not.
///
/// Note that displaying line numbers may be less performant than not doing it.
/// `minus` tries to do as quickly as possible but the numbers and padding
/// still have to be computed.
///
/// This implements [`Not`](std::ops::Not) to allow turning on/off line numbers
/// when they where not locked in by the binary displaying the text.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum LineNumbers {
    /// Enable line numbers permanently, cannot be turned off by user.
    AlwaysOn,
    /// Line numbers should be turned on, although users can turn it off
    /// (i.e, set it to `Disabled`).
    Enabled,
    /// Line numbers should be turned off, although users can turn it on
    /// (i.e, set it to `Enabled`).
    Disabled,
    /// Disable line numbers permanently, cannot be turned on by user.
    AlwaysOff,
}

impl LineNumbers {
    const EXTRA_PADDING: usize = 5;

    /// Returns `true` if `self` can be inverted (i.e, `!self != self`), see
    /// the documentation for the variants to know if they are invertible or
    /// not.
    #[allow(dead_code)]
    const fn is_invertible(self) -> bool {
        matches!(self, Self::Enabled | Self::Disabled)
    }

    const fn is_on(self) -> bool {
        matches!(self, Self::Enabled | Self::AlwaysOn)
    }
}

impl std::ops::Not for LineNumbers {
    type Output = Self;

    fn not(self) -> Self::Output {
        use LineNumbers::{Disabled, Enabled};

        match self {
            Enabled => Disabled,
            Disabled => Enabled,
            ln => ln,
        }
    }
}

#[cfg(test)]
mod tests;