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 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
//! A fast, asynchronous terminal paging library for Rust. `minus` provides high //! level functionalities to easily write a pager for any terminal application. //! Due to the asynchronous nature of `minus`, the pager's data can be //! **updated** (this needs the correct feature to be enabled). //! //! `minus` supports both [`tokio`] as well as [`async-std`] runtimes. What's //! more, if you only want to use `minus` for serving static output, you can //! simply opt out of these dynamic features, see the //! [**Features**](crate#features) section below. //! //! ## Why this crate ? //! //! `minus` was started by me for my work on [`pijul`]. I was unsatisfied with //! the existing options like [`pager`] and [`moins`]. //! //! * [`pager`]: //! * Only provides functions to join the standard output of the current //! program to the standard input of external pager like `more` or `less`. //! * Due to this, to work within Windows, the external pagers need to be //! packaged along with the executable. //! //! * [`moins`]: //! * The output could only be defined once and for all. It is not asynchronous //! and does not support updating. //! //! The main goals of `minus` are to be very compact and as configurable as possible. //! * `minus` provides a lot of configurablity to the end-application and this //! configuration can be defined not just in compile-time but also in **runtime.** Your //! entire configuration like the output displayed, prompt and line numbers are inside //! a `Arc<Mutex>`, which means at any time you can lock the configuration, change //! something, and voila minus will automatically update the screen //! //! * When using `minus`, you select what features you need and **nothing else**. See //! [Features](crate#features) below //! //! [`tokio`]: https://crates.io/crates/tokio //! [`async-std`]: https://crates.io/crates/async-std //! [`pager`]: https://crates.io/crates/pager //! [`moins`]: https://crates.io/crates/moins //! [`pijul`]: https://pijul.org/ //! //! ## Features //! //! * `async_std_lib`: Use this if you use [`async_std`] runtime in your //! application //! * `tokio_lib`:Use this if you are using [`tokio`] runtime for your application //! * `static_output`: Use this if you only want to use `minus` for displaying static //! output //! * `search`: If you want searching capablities inside the feature // 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 = "tokio_lib", feature = "async_std_lib", feature = "static_output" )), allow(unused_imports), allow(dead_code) )] #![deny(clippy::all)] #![warn(clippy::pedantic)] #[cfg(all(feature = "tokio_lib", feature = "async_std_lib"))] compile_error!("Only tokio, or async_std_lib can be enabled at a time"); mod error; mod input; #[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))] mod rt_wrappers; // #[cfg(feature = "search")] mod search; #[cfg(feature = "static_output")] mod static_pager; mod utils; #[cfg(feature = "search")] pub use utils::SearchMode; pub use utils::InputEvent; pub use input::{DefaultInputHandler, InputHandler}; #[cfg(feature = "static_output")] pub use static_pager::page_all; #[cfg(feature = "async_std_lib")] pub use {async_std::sync::Mutex, rt_wrappers::async_std_wrapper::async_std_updating}; #[cfg(feature = "tokio_lib")] pub use {rt_wrappers::tokio_wrapper::tokio_updating, tokio::sync::Mutex}; #[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))] use std::sync::Arc; pub use error::*; pub use utils::LineNumbers; mod init; #[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))] pub type PagerMutex = Mutex<Pager>; pub type ExitCallbacks = Vec<Box<dyn FnMut() + Send + Sync + 'static>>; /// A struct containing basic configurations for the pager. This is used by /// all initializing functions // #[derive(Clone)] pub struct Pager { /// The output that is displayed pub lines: String, /// Configuration for line numbers. See [`LineNumbers`] line_numbers: LineNumbers, /// The prompt displayed at the bottom pub prompt: String, // has all the data been sent to the pager pub data_finished: bool, // trait which will handle mapping crossterm input, into minus events pub input_handler: Box<dyn InputHandler + Send + Sync>, // callbacks which will be called when we finish on_exit_callbacks: ExitCallbacks, /// The behaviour to do when user quits the program using `q` or `Ctrl+C` /// See [`ExitStrategy`] for available options exit_strategy: ExitStrategy, /// The upper mark of scrolling. It is kept private to prevent end-applications /// from mutating this upper_mark: usize, /// Do we want to actually page if we dont have more than a screen of output page_if_havent_overflowed: bool, /// Tells whether the searching is possible inside the pager /// /// This is a candidate for deprecation. If you want to enable search, enable the /// `search` feature. This is because this dosen't really give any major benifits /// since `regex` and all related functions are already compiled searchable: bool, /// Stores the most recent search term #[cfg(feature = "search")] search_term: String, /// A temporary space to store modifications to the lines string #[cfg(feature = "search")] search_lines: String, } impl Pager { /// Initialize a new pager configuration /// /// Example /// ``` /// let pager = minus::Pager::new(); /// ``` #[must_use] pub fn new() -> Self { let input_handler = Box::new(DefaultInputHandler {}); Pager { lines: String::new(), line_numbers: LineNumbers::Disabled, upper_mark: 0, prompt: "minus".to_string(), exit_strategy: ExitStrategy::ProcessQuit, data_finished: false, on_exit_callbacks: Vec::new(), page_if_havent_overflowed: true, input_handler, searchable: true, #[cfg(feature = "search")] search_term: String::new(), #[cfg(feature = "search")] search_lines: String::new(), } } /// Set the output text to this `t` /// Example /// ``` /// let pager = minus::Pager::new().set_text("This is a line"); /// ``` pub fn set_text(mut self, t: impl Into<String>) -> Self { self.lines = t.into(); self } /// Set line number to this setting /// /// Example /// ``` /// use minus::{Pager, LineNumbers}; /// /// let pager = Pager::new().set_line_numbers(LineNumbers::Enabled); /// ``` #[must_use] pub fn set_line_numbers(mut self, l: LineNumbers) -> Self { self.line_numbers = l; self } /// Set the prompt displayed at the prompt to `t` /// /// Example /// ``` /// use minus::Pager; /// /// let pager = Pager::new().set_prompt("my awesome program"); /// ``` pub fn set_prompt(mut self, t: impl Into<String>) -> Self { self.prompt = t.into(); self } /// Sets whether searching is possible inside the pager. Default s set to true /// /// Example /// ``` /// use minus::Pager; /// /// let pager = Pager::new().set_searchable(false); /// ``` /// /// This is a candidate for deprecation. If you want to enable search, enable the /// `search` feature. This is because this dosen't really give any major benifits /// since `regex` and all related functions are already compiled #[must_use] pub fn set_searchable(mut self, s: bool) -> Self { self.searchable = s; self } /// Sets whether the pager actually blocks UI if our data is finished, and we havent overflowed the page /// /// Example /// ``` /// use minus::Pager; /// /// let pager = Pager::new().set_page_if_havent_overflowed(false); /// ``` #[must_use] pub fn set_page_if_havent_overflowed(mut self, p: bool) -> Self { self.page_if_havent_overflowed = p; self } /// Return a [`PagerMutex`] from this [`Pager`]. This is gated on `tokio_lib` or /// `async_std_lib` feature /// /// Example /// ``` /// use minus::Pager; /// /// let pager = Pager::new().set_text("This output is paged").finish(); /// ``` #[must_use] #[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))] pub fn finish(self) -> Arc<PagerMutex> { Arc::new(PagerMutex::new(self)) } /// Set the default exit strategy. /// /// This controls how the pager will behave when the user presses `q` or `Ctrl+C` /// See [`ExitStrategy`] for available options #[must_use] pub fn set_exit_strategy(mut self, strategy: ExitStrategy) -> Self { self.exit_strategy = strategy; self } /// Set the [`InputHandler`] that maps from crossterm input events /// to minuses internal events, this allows custom keybindings /// #[must_use] pub fn set_input_handler(mut self, input_handler: Box<dyn InputHandler + Send + Sync>) -> Self { self.input_handler = input_handler; self } /// Returns the appropriate text for displaying. /// /// Nrmally it will return `self.lines` /// In case a search, `self.search_lines` is returned pub(crate) fn get_lines(&self) -> String { #[cfg(feature = "search")] if self.search_term.is_empty() { self.lines.clone() } else { self.search_lines.clone() } #[cfg(not(feature = "search"))] self.lines.clone() } /// Indicate to the pager that the data has finished. /// This is currently only used by the async runtimes. pub fn data_finished(&mut self) { self.data_finished = true; } /// Indicate to the pager that we want to exit pub fn exit(&mut self) { self.run_exit_callbacks(); } /// Add a callback to be run when the pager has finished pub fn add_exit_callback<F>(&mut self, callback: F) where F: FnMut() + Send + Sync + 'static, { self.on_exit_callbacks.push(Box::new(callback)); } /// Run callbacks that want to be run on exit pub fn run_exit_callbacks(&mut self) { for exit_callback in &mut self.on_exit_callbacks { exit_callback(); } } } impl std::default::Default for Pager { fn default() -> Self { Pager::new() } } /// Behaviour that happens when the pager is exitted #[derive(PartialEq, Clone)] pub enum ExitStrategy { /// Kill the entire application immediately. /// /// This is the prefered 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 prefered 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, }