1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +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; extern crate uucore;
use std::{ use std::{
convert::TryInto,
fs::File, fs::File,
io::{stdin, stdout, BufReader, Read, Stdout, Write}, io::{stdin, stdout, BufReader, Read, Stdout, Write},
path::Path, 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) { fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap(); let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols)); let lines = break_buff(buff, usize::from(cols));
let line_count: u16 = lines.len().try_into().unwrap();
let mut upper_mark = 0; let mut pager = Pager::new(rows as usize, lines, next_file, silent);
let mut lines_left = line_count.saturating_sub(upper_mark + rows); pager.draw(stdout, false);
let mut wrong_key = false; if pager.should_close() {
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; return;
} else {
to_be_done = true;
}
} }
loop { loop {
let mut wrong_key = false;
if event::poll(Duration::from_millis(10)).unwrap() { if event::poll(Duration::from_millis(10)).unwrap() {
wrong_key = false;
match event::read().unwrap() { match event::read().unwrap() {
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Char('q'), 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(' '), code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1)); pager.page_down();
} }
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Up, code: KeyCode::Up,
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
}) => { }) => {
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1)); pager.page_up();
} }
_ => { _ => {
wrong_key = true; 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 { pager.draw(stdout, wrong_key);
if to_be_done || is_last { if pager.should_close() {
return; return;
} }
to_be_done = true;
}
} }
} }
} }
#[allow(clippy::too_many_arguments)] struct Pager<'a> {
fn draw( // The current line at the top of the screen
upper_mark: &mut u16, upper_mark: usize,
rows: u16, // The number of rows that fit on the screen
mut stdout: &mut std::io::Stdout, content_rows: usize,
lines: Vec<String>, lines: Vec<String>,
lc: u16, next_file: Option<&'a str>,
next_file: Option<&str>, line_count: usize,
close_on_down: bool,
silent: bool, silent: bool,
wrong_key: bool, }
) {
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(); execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc); let displayed_lines = self
// Reduce the row by 1 for the prompt .lines
let displayed_lines = lines
.iter() .iter()
.skip(up_mark.into()) .skip(self.upper_mark)
.take(usize::from(rows.saturating_sub(1))); .take(self.content_rows);
for line in displayed_lines { for line in displayed_lines {
stdout stdout
.write_all(format!("\r{}\n", line).as_bytes()) .write_all(format!("\r{}\n", line).as_bytes())
.unwrap(); .unwrap();
} }
make_prompt_and_flush(&mut stdout, lower_mark, lc, next_file, silent, wrong_key); }
*upper_mark = up_mark;
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();
}
} }
// Break the lines on the cols of the terminal // Break the lines on the cols of the terminal
@ -364,69 +400,11 @@ fn break_line(line: &str, cols: usize) -> Vec<String> {
lines 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)] #[cfg(test)]
mod tests { mod tests {
use super::{break_line, calc_range}; use super::break_line;
use unicode_width::UnicodeWidthStr; 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] #[test]
fn test_break_lines_long() { fn test_break_lines_long() {
let mut test_string = String::with_capacity(100); let mut test_string = String::with_capacity(100);