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:
commit
7f754dc1a5
1 changed files with 107 additions and 129 deletions
|
@ -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() {
|
||||||
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
}
|
||||||
) {
|
|
||||||
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 {
|
impl<'a> Pager<'a> {
|
||||||
stdout
|
fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>, silent: bool) -> Self {
|
||||||
.write_all(format!("\r{}\n", line).as_bytes())
|
let line_count = lines.len();
|
||||||
.unwrap();
|
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
|
// 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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue