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
// There maybe a lot of junk that may get imported if not needed. So we allow
// unused imports
#![allow(unused_imports)]
#[cfg(feature = "search")]
use crate::search;
#[cfg(feature = "search")]
use crate::utils::SearchMode;
use crate::utils::{cleanup, draw, handle_input, setup, InputEvent};
#[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))]
use crate::PagerMutex;
use crate::{error::AlternateScreenPagingError, Pager};
use crossterm::{cursor::MoveTo, event};
#[cfg(feature = "search")]
use std::convert::{TryFrom, TryInto};
use std::io::{self, Write as _};
#[cfg(any(feature = "tokio_lib", feature = "async_std_lib"))]
use std::sync::Arc;
#[cfg(feature = "static_output")]
#[allow(clippy::clippy::too_many_lines)]
pub(crate) fn static_paging(mut pager: Pager) -> Result<(), AlternateScreenPagingError> {
// Setup terminal
let mut out = io::stdout();
let mut rows = setup(&out, false)?;
#[allow(unused_assignments)]
let mut redraw = true;
#[cfg(feature = "search")]
let mut s_co: Vec<(u16, u16)> = Vec::new();
#[cfg(feature = "search")]
let mut s_mark = -1;
#[cfg(feature = "search")]
let mut search_mode = SearchMode::Unknown;
draw(&mut out, &mut pager, rows)?;
loop {
// Check for events
if event::poll(std::time::Duration::from_millis(10))
.map_err(|e| AlternateScreenPagingError::HandleEvent(e.into()))?
{
// Get the events
let input = handle_input(
event::read().map_err(|e| AlternateScreenPagingError::HandleEvent(e.into()))?,
pager.upper_mark,
#[cfg(feature = "search")]
search_mode,
pager.line_numbers,
rows,
);
// Update any data that may have changed
#[allow(clippy::clippy::match_same_arms)]
match input {
Some(InputEvent::Exit) => return Ok(cleanup(out, &pager.exit_strategy)?),
Some(InputEvent::UpdateRows(r)) => {
rows = r;
redraw = true;
}
Some(InputEvent::UpdateUpperMark(um)) => {
pager.upper_mark = um;
redraw = true;
}
Some(InputEvent::UpdateLineNumber(l)) => {
pager.line_numbers = l;
redraw = true;
}
// These are same as their dynamic counterparts, except for the fact
// that they use a blocking fetch_input function
#[cfg(feature = "search")]
Some(InputEvent::Search(m)) => {
search_mode = m;
let string = search::fetch_input_blocking(&mut out, search_mode, rows)?;
if !string.is_empty() {
s_co = search::highlight_search(&mut pager, &string)
.map_err(|e| AlternateScreenPagingError::SearchExpError(e.into()))?;
pager.search_term = string;
}
redraw = true;
}
#[cfg(feature = "search")]
Some(InputEvent::NextMatch) if !pager.search_term.is_empty() => {
s_mark += 1;
if isize::try_from(s_co.len()).unwrap() > s_mark {
let (mut x, mut y) = (None, None);
while let Some(co) = s_co.get(usize::try_from(s_mark).unwrap()) {
if usize::from(co.1) < pager.upper_mark {
s_mark += 1;
} else {
x = Some(co.0);
y = Some(co.1);
break;
}
}
if x.is_none() || y.is_none() {
continue;
}
if usize::from(y.unwrap()) >= pager.upper_mark + rows {
pager.upper_mark = y.unwrap().into();
}
draw(&mut out, &mut pager, rows)?;
y = Some(
y.unwrap()
.saturating_sub(pager.upper_mark.try_into().unwrap()),
);
write!(out, "{}", MoveTo(x.unwrap(), y.unwrap()))?;
out.flush()?;
// Do not redraw the console
redraw = false;
}
}
#[cfg(feature = "search")]
Some(InputEvent::PrevMatch) if !pager.search_term.is_empty() => {
if isize::try_from(s_co.len()).unwrap() > s_mark {
// If s_mark is less than 0, make it 0, else subtract 1 from it
s_mark = if s_mark <= 0 {
0
} else {
s_mark.saturating_sub(1)
};
// Do the same steps that we have did in NextMatch block
let (x, mut y) = s_co[usize::try_from(s_mark).unwrap()];
if usize::from(y) <= pager.upper_mark {
pager.upper_mark = y.into();
}
draw(&mut out, &mut pager, rows)?;
y = y.saturating_sub(pager.upper_mark.try_into().unwrap());
write!(out, "{}", MoveTo(x, y))?;
out.flush()?;
redraw = false;
}
}
#[cfg(feature = "search")]
Some(_) => continue,
None => continue,
}
if redraw {
draw(&mut out, &mut pager, rows)?;
}
}
}
}
/// Runs the pager in dynamic mode for the `PagerMutex`.
///
/// `get` is a function that will extract the Pager lock from the
/// `PageMutex`. `get` is only called when drawing, Therefore, it can be mutated the entire time, except while drawing
///
/// ## Errors
///
/// Setting/cleaning up the terminal can fail and IO to/from the terminal can
/// fail.
#[cfg(any(feature = "async_std_lib", feature = "tokio_lib"))]
#[allow(clippy::clippy::too_many_lines)]
pub(crate) async fn dynamic_paging(
p: &Arc<PagerMutex>,
) -> std::result::Result<(), AlternateScreenPagingError> {
// Setup terminal
let mut out = io::stdout();
let mut rows = setup(&out, true)?;
// Search related variables
// Vector of match coordinates
// Earch element is a (x,y) pair, where the cursor will be placed
#[cfg(feature = "search")]
let mut s_co: Vec<(u16, u16)> = Vec::new();
// A marker of which element of s_co we are currently at
// -1 means we have just highlighted all the matches but the cursor has not been
// placed in any one of them
#[cfg(feature = "search")]
let mut s_mark = -1;
// Search Mode
#[cfg(feature = "search")]
let mut search_mode = SearchMode::Unknown;
// Whether to redraw the console
#[allow(unused_assignments)]
let mut redraw = true;
let mut last_line_count = 0;
loop {
// Get the lock, clone it and immidiately drop the lock
let mut guard = p.lock().await;
// Display the text continously if last displayed line count is not same and
// all rows are not filled
let line_count = guard.lines.lines().count();
let have_just_overflowed = (last_line_count < rows) && (line_count >= rows);
if last_line_count != line_count && ((line_count < rows) || have_just_overflowed) {
draw(&mut out, &mut guard, rows)?;
last_line_count = line_count;
}
drop(guard);
// Check for events
if event::poll(std::time::Duration::from_millis(10))
.map_err(|e| AlternateScreenPagingError::HandleEvent(e.into()))?
{
// Lock the value again
let mut lock = p.lock().await;
// Get the events
let input = handle_input(
event::read().map_err(|e| AlternateScreenPagingError::HandleEvent(e.into()))?,
lock.upper_mark,
#[cfg(feature = "search")]
search_mode,
lock.line_numbers,
rows,
);
// Update any data that may have changed
match input {
Some(InputEvent::Exit) => return Ok(cleanup(out, &lock.exit_strategy)?),
Some(InputEvent::UpdateRows(r)) => {
rows = r;
redraw = true;
}
Some(InputEvent::UpdateUpperMark(um)) => {
lock.upper_mark = um;
redraw = true;
}
Some(InputEvent::UpdateLineNumber(l)) => {
lock.line_numbers = l;
redraw = true;
}
#[cfg(feature = "search")]
Some(InputEvent::Search(m)) if lock.searchable => {
search_mode = m;
// Fetch the search query asynchronously
let string = search::fetch_input(&mut out, search_mode, rows).await?;
if !string.is_empty() {
// If the string is not empty, highlight all instances of the
// match and return a vector of match coordinates
s_co = search::highlight_search(&mut lock, &string)
.map_err(|e| AlternateScreenPagingError::SearchExpError(e.into()))?;
// Update the search term
lock.search_term = string;
}
redraw = true;
}
#[cfg(feature = "search")]
Some(InputEvent::NextMatch) if !lock.search_term.is_empty() => {
// Increment the search mark
s_mark += 1;
// These unwrap operations should be safe
// Make sure s_mark is not greater than s_co's lenght
if isize::try_from(s_co.len()).unwrap() > s_mark {
// Get the next coordinates
// Make sure that the next match taken to is after the
// current upper_mark
let (mut x, mut y) = (None, None);
while let Some(co) = s_co.get(usize::try_from(s_mark).unwrap()) {
if usize::from(co.1) < lock.upper_mark {
s_mark += 1;
} else {
x = Some(co.0);
y = Some(co.1);
break;
}
}
if x.is_none() || y.is_none() {
continue;
}
if usize::from(y.unwrap()) >= lock.upper_mark + rows {
lock.upper_mark = y.unwrap().into();
}
draw(&mut out, &mut lock, rows)?;
y = Some(
y.unwrap()
.saturating_sub(lock.upper_mark.try_into().unwrap()),
);
write!(out, "{}", MoveTo(x.unwrap(), y.unwrap()))?;
out.flush()?;
// Do not redraw the console
redraw = false;
}
}
#[cfg(feature = "search")]
Some(InputEvent::PrevMatch) if !lock.search_term.is_empty() => {
if isize::try_from(s_co.len()).unwrap() > s_mark {
// If s_mark is less than 0, make it 0, else subtract 1 from it
s_mark = if s_mark <= 0 {
0
} else {
s_mark.saturating_sub(1)
};
// Do the same steps that we have did in NextMatch block
let (x, mut y) = s_co[usize::try_from(s_mark).unwrap()];
if usize::from(y) <= lock.upper_mark {
lock.upper_mark = y.into();
}
draw(&mut out, &mut lock, rows)?;
y = y.saturating_sub(lock.upper_mark.try_into().unwrap());
write!(out, "{}", MoveTo(x, y))?;
out.flush()?;
redraw = false;
}
}
#[cfg(feature = "search")]
Some(_) => continue,
None => continue,
}
// If redraw is true, then redraw the screen
if redraw {
draw(&mut out, &mut lock, rows)?;
}
}
}
}