1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2382 from tertsdiepraam/more/rewrite-drawing-logic

`more`: rewrite drawing logic
This commit is contained in:
Terts Diepraam 2021-06-12 11:12:29 +02:00 committed by GitHub
commit 7f754dc1a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -11,7 +11,6 @@
extern crate uucore;
use std::{
convert::TryInto,
fs::File,
io::{stdin, stdout, BufReader, Read, Stdout, Write},
path::Path,
@ -210,40 +209,16 @@ fn reset_term(_: &mut usize) {}
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols));
let line_count: u16 = lines.len().try_into().unwrap();
let mut upper_mark = 0;
let mut lines_left = line_count.saturating_sub(upper_mark + rows);
let mut wrong_key = false;
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
silent,
wrong_key,
);
let is_last = next_file.is_none();
// Specifies whether we have reached the end of the file and should
// return on the next key press. However, we immediately return when
// this is the last file.
let mut to_be_done = false;
if lines_left == 0 && is_last {
if is_last {
return;
} else {
to_be_done = true;
}
let mut pager = Pager::new(rows as usize, lines, next_file, silent);
pager.draw(stdout, false);
if pager.should_close() {
return;
}
loop {
let mut wrong_key = false;
if event::poll(Duration::from_millis(10)).unwrap() {
wrong_key = false;
match event::read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
@ -264,66 +239,127 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo
code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE,
}) => {
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1));
pager.page_down();
}
Event::Key(KeyEvent {
code: KeyCode::Up,
modifiers: KeyModifiers::NONE,
}) => {
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1));
pager.page_up();
}
_ => {
wrong_key = true;
}
}
lines_left = line_count.saturating_sub(upper_mark + rows);
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
next_file,
silent,
wrong_key,
);
if lines_left == 0 {
if to_be_done || is_last {
return;
}
to_be_done = true;
pager.draw(stdout, wrong_key);
if pager.should_close() {
return;
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw(
upper_mark: &mut u16,
rows: u16,
mut stdout: &mut std::io::Stdout,
struct Pager<'a> {
// The current line at the top of the screen
upper_mark: usize,
// The number of rows that fit on the screen
content_rows: usize,
lines: Vec<String>,
lc: u16,
next_file: Option<&str>,
next_file: Option<&'a str>,
line_count: usize,
close_on_down: bool,
silent: bool,
wrong_key: bool,
) {
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc);
// Reduce the row by 1 for the prompt
let displayed_lines = lines
.iter()
.skip(up_mark.into())
.take(usize::from(rows.saturating_sub(1)));
}
for line in displayed_lines {
stdout
.write_all(format!("\r{}\n", line).as_bytes())
.unwrap();
impl<'a> Pager<'a> {
fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
let line_count = lines.len();
Self {
upper_mark: 0,
content_rows: rows - 1,
lines,
next_file,
line_count,
close_on_down: false,
silent,
}
}
fn should_close(&mut self) -> bool {
if self.upper_mark + self.content_rows >= self.line_count {
if self.close_on_down {
return true;
}
if self.next_file.is_none() {
return true;
} else {
self.close_on_down = true;
}
} else {
self.close_on_down = false;
}
false
}
fn page_down(&mut self) {
self.upper_mark += self.content_rows;
}
fn page_up(&mut self) {
self.upper_mark = self.upper_mark.saturating_sub(self.content_rows);
}
fn draw(&self, stdout: &mut std::io::Stdout, wrong_key: bool) {
let lower_mark = self.line_count.min(self.upper_mark + self.content_rows);
self.draw_lines(stdout);
self.draw_prompt(stdout, lower_mark, wrong_key);
stdout.flush().unwrap();
}
fn draw_lines(&self, stdout: &mut std::io::Stdout) {
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let displayed_lines = self
.lines
.iter()
.skip(self.upper_mark)
.take(self.content_rows);
for line in displayed_lines {
stdout
.write_all(format!("\r{}\n", line).as_bytes())
.unwrap();
}
}
fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize, wrong_key: bool) {
let status_inner = if lower_mark == self.line_count {
format!("Next file: {}", self.next_file.unwrap_or_default())
} else {
format!(
"{}%",
(lower_mark as f64 / self.line_count as f64 * 100.0).round() as u16
)
};
let status = format!("--More--({})", status_inner);
let banner = match (self.silent, wrong_key) {
(true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(),
(true, false) => format!("{}[Press space to continue, 'q' to quit.]", status),
(false, true) => format!("{}{}", status, BELL),
(false, false) => status,
};
write!(
stdout,
"\r{}{}{}",
Attribute::Reverse,
banner,
Attribute::Reset
)
.unwrap();
}
make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file, silent, wrong_key);
*upper_mark = up_mark;
}
// Break the lines on the cols of the terminal
@ -364,69 +400,11 @@ fn break_line(line: &str, cols: usize) -> Vec<String> {
lines
}
// Calculate upper_mark based on certain parameters
fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) {
let mut lower_mark = upper_mark.saturating_add(rows);
if lower_mark >= line_count {
upper_mark = line_count.saturating_sub(rows).saturating_add(1);
lower_mark = line_count;
} else {
lower_mark = lower_mark.saturating_sub(1)
}
(upper_mark, lower_mark)
}
// Make a prompt similar to original more
fn make_prompt_and_flush(
stdout: &mut Stdout,
lower_mark: u16,
lc: u16,
next_file: Option<&str>,
silent: bool,
wrong_key: bool,
) {
let status_inner = if lower_mark == lc {
format!("Next file: {}", next_file.unwrap_or_default())
} else {
format!(
"{}%",
(lower_mark as f64 / lc as f64 * 100.0).round() as u16
)
};
let status = format!("--More--({})", status_inner);
let banner = match (silent, wrong_key) {
(true, true) => "[Press 'h' for instructions. (unimplemented)]".to_string(),
(true, false) => format!("{}[Press space to continue, 'q' to quit.]", status),
(false, true) => format!("{}{}", status, BELL),
(false, false) => status,
};
write!(
stdout,
"\r{}{}{}",
Attribute::Reverse,
banner,
Attribute::Reset
)
.unwrap();
stdout.flush().unwrap();
}
#[cfg(test)]
mod tests {
use super::{break_line, calc_range};
use super::break_line;
use unicode_width::UnicodeWidthStr;
// It is good to test the above functions
#[test]
fn test_calc_range() {
assert_eq!((0, 24), calc_range(0, 25, 100));
assert_eq!((50, 74), calc_range(50, 25, 100));
assert_eq!((76, 100), calc_range(85, 25, 100));
}
#[test]
fn test_break_lines_long() {
let mut test_string = String::with_capacity(100);