pager_rs/
lib.rs

1/*!
2Cross-platform, customizable terminal pager library for rust.
3
4# pager-rs can be used to:
5- show text content with too many lines.
6- dialog with user using [`custom commands`].
7
8# Usage
9See: [examples]
10
11[examples]: https://github.com/ketenburhan/pager-rs/tree/main/examples
12[`custom commands`]: Command
13*/
14
15#![warn(missing_docs)]
16
17use crossterm::{
18    cursor,
19    event::{self, Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind},
20    execute, queue,
21    style::Print,
22    terminal::{self, disable_raw_mode, enable_raw_mode, Clear, ClearType},
23};
24use std::io::{stdin, stdout, Write};
25
26mod status_bar;
27pub use status_bar::*;
28mod state;
29pub use state::*;
30
31/// Run a [`State`]
32pub fn run(state: &mut State) -> std::io::Result<()> {
33    let mut out = stdout();
34    disable_raw_mode()?;
35    execute!(
36        out,
37        terminal::Clear(ClearType::All),
38        cursor::MoveTo(0, 0),
39        Print(state.get_visible()),
40        cursor::MoveTo(0, state.size.1 - state.status_bar.line_layouts.len() as u16),
41        Print(state.status_bar.get_visible(state))
42    )?;
43    enable_raw_mode()?;
44
45    while state.running {
46        let read_event = event::read()?;
47        let flush = match read_event {
48            Event::Key(KeyEvent { code, .. }) => match code {
49                KeyCode::Char(':') => {
50                    disable_raw_mode()?;
51                    execute!(
52                        out,
53                        cursor::MoveTo(0, state.size.1 - 1),
54                        Clear(ClearType::CurrentLine),
55                        cursor::Show,
56                        Print(":")
57                    )?;
58                    let mut buf = String::new();
59                    stdin().read_line(&mut buf)?;
60                    let buf = buf.lines().next().unwrap();
61
62                    let found = state.commands.0.clone().into_iter().find(
63                        |command| matches!(command, Command { cmd, .. } if cmd.contains(&CommandType::Colon(buf.to_string()))),
64                    );
65                    let retrn = if let Some(Command { func, .. }) = found {
66                        func(state)
67                    } else {
68                        false
69                    };
70
71                    execute!(out, Print(retrn), cursor::Hide)?;
72                    enable_raw_mode()?;
73                    retrn
74                }
75                code => state.match_key_event(code),
76            },
77            Event::Mouse(ev) => match ev {
78                MouseEvent {
79                    kind: MouseEventKind::ScrollUp,
80                    ..
81                } => state.up(),
82                MouseEvent {
83                    kind: MouseEventKind::ScrollDown,
84                    ..
85                } => state.down(),
86                _ => false,
87            },
88            Event::Resize(x, y) => {
89                state.size = (x, y);
90                true
91            }
92            _ => false,
93        };
94        if flush {
95            disable_raw_mode()?;
96            queue!(
97                out,
98                cursor::MoveTo(0, 0),
99                terminal::Clear(ClearType::All),
100                Print(state.get_visible()),
101                cursor::MoveTo(0, state.size.1 - state.status_bar.line_layouts.len() as u16),
102                Print(state.status_bar.get_visible(state)),
103            )?;
104            out.flush()?;
105            enable_raw_mode()?;
106        }
107    }
108
109    disable_raw_mode()?;
110
111    Ok(())
112}
113
114/// Setup terminal for running [`State`].
115/// Enter alternate screen, enable mouse capture, hide the cursor.
116///
117/// This function must be called before the [`run`] function.
118pub fn init() -> std::io::Result<()> {
119    let mut out = stdout();
120    execute!(
121        out,
122        terminal::EnterAlternateScreen,
123        event::EnableMouseCapture,
124        cursor::Hide,
125    )
126}
127
128/// Undo [`init`].
129/// Leave alternate screen, disable mouse capture, show the cursor.
130pub fn finish() -> std::io::Result<()> {
131    let mut out = stdout();
132    execute!(
133        out,
134        event::DisableMouseCapture,
135        terminal::LeaveAlternateScreen,
136        cursor::Show
137    )
138}