dialoguer/
paging.rs

1use console::Term;
2
3use crate::Result;
4
5/// Creates a paging module
6///
7/// The paging module serves as tracking structure to allow paged views
8/// and automatically (de-)activates paging depending on the current terminal size.
9pub struct Paging<'a> {
10    pub pages: usize,
11    pub current_page: usize,
12    pub capacity: usize,
13    pub active: bool,
14    pub max_capacity: Option<usize>,
15    term: &'a Term,
16    current_term_size: (u16, u16),
17    items_len: usize,
18    activity_transition: bool,
19}
20
21impl<'a> Paging<'a> {
22    pub fn new(term: &'a Term, items_len: usize, max_capacity: Option<usize>) -> Paging<'a> {
23        let term_size = term.size();
24        // Subtract -2 because we need space to render the prompt, if paging is active
25        let capacity = max_capacity
26            .unwrap_or(std::usize::MAX)
27            .clamp(3, term_size.0 as usize)
28            - 2;
29        let pages = (items_len as f64 / capacity as f64).ceil() as usize;
30
31        Paging {
32            pages,
33            current_page: 0,
34            capacity,
35            active: pages > 1,
36            term,
37            current_term_size: term_size,
38            items_len,
39            max_capacity,
40            // Set transition initially to true to trigger prompt rendering for inactive paging on start
41            activity_transition: true,
42        }
43    }
44
45    pub fn update_page(&mut self, cursor_pos: usize) {
46        if cursor_pos != !0
47            && (cursor_pos < self.current_page * self.capacity
48                || cursor_pos >= (self.current_page + 1) * self.capacity)
49        {
50            self.current_page = cursor_pos / self.capacity;
51        }
52    }
53
54    /// Updates all internal based on the current terminal size and cursor position
55    pub fn update(&mut self, cursor_pos: usize) -> Result {
56        let new_term_size = self.term.size();
57
58        if self.current_term_size != new_term_size {
59            self.current_term_size = new_term_size;
60            self.capacity = self
61                .max_capacity
62                .unwrap_or(std::usize::MAX)
63                .clamp(3, self.current_term_size.0 as usize)
64                - 2;
65            self.pages = (self.items_len as f64 / self.capacity as f64).ceil() as usize;
66        }
67
68        if self.active == (self.pages > 1) {
69            self.activity_transition = false;
70        } else {
71            self.active = self.pages > 1;
72            self.activity_transition = true;
73            // Clear everything to prevent "ghost" lines in terminal when a resize happened
74            self.term.clear_last_lines(self.capacity)?;
75        }
76
77        self.update_page(cursor_pos);
78
79        Ok(())
80    }
81
82    /// Renders a prompt when the following conditions are met:
83    /// * Paging is active
84    /// * Transition of the paging activity happened (active -> inactive / inactive -> active)
85    pub fn render_prompt<F>(&mut self, mut render_prompt: F) -> Result
86    where
87        F: FnMut(Option<(usize, usize)>) -> Result,
88    {
89        if self.active {
90            let paging_info = Some((self.current_page + 1, self.pages));
91            render_prompt(paging_info)?;
92        } else if self.activity_transition {
93            render_prompt(None)?;
94        }
95
96        self.term.flush()?;
97
98        Ok(())
99    }
100
101    /// Navigates to the next page
102    pub fn next_page(&mut self) -> usize {
103        if self.current_page == self.pages - 1 {
104            self.current_page = 0;
105        } else {
106            self.current_page += 1;
107        }
108
109        self.current_page * self.capacity
110    }
111
112    /// Navigates to the previous page
113    pub fn previous_page(&mut self) -> usize {
114        if self.current_page == 0 {
115            self.current_page = self.pages - 1;
116        } else {
117            self.current_page -= 1;
118        }
119
120        self.current_page * self.capacity
121    }
122}