mirror of
https://github.com/uutils/coreutils
synced 2024-07-21 18:04:45 +00:00
commit
6141fdcc73
152
Cargo.lock
generated
152
Cargo.lock
generated
|
@ -224,6 +224,7 @@ dependencies = [
|
|||
name = "coreutils"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"conv",
|
||||
"filetime",
|
||||
"glob 0.3.0",
|
||||
|
@ -487,6 +488,53 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||
dependencies = [
|
||||
"memchr 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.20"
|
||||
|
@ -713,6 +761,15 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ioctl-sys"
|
||||
version = "0.5.2"
|
||||
|
@ -768,6 +825,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
|
@ -828,6 +894,28 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.13.1"
|
||||
|
@ -859,6 +947,15 @@ version = "0.1.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.0"
|
||||
|
@ -971,6 +1068,31 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.8",
|
||||
"smallvec 1.6.1",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.18"
|
||||
|
@ -1372,6 +1494,26 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.6.14"
|
||||
|
@ -1381,6 +1523,12 @@ dependencies = [
|
|||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
|
@ -1840,7 +1988,7 @@ dependencies = [
|
|||
"paste",
|
||||
"quickcheck",
|
||||
"rand 0.7.3",
|
||||
"smallvec",
|
||||
"smallvec 0.6.14",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2067,7 +2215,9 @@ dependencies = [
|
|||
name = "uu_more"
|
||||
version = "0.0.6"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"nix 0.13.1",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_termios",
|
||||
|
|
|
@ -348,6 +348,7 @@ time = "0.1"
|
|||
unindent = "0.1"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
|
||||
walkdir = "2.2"
|
||||
atty = "0.2.14"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
rust-users = { version="0.10", package="users" }
|
||||
|
|
|
@ -18,6 +18,8 @@ path = "src/more.rs"
|
|||
clap = "2.33"
|
||||
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
|
||||
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
|
||||
crossterm = ">=0.19"
|
||||
atty = "0.2.14"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox_termios = "0.1"
|
||||
|
|
|
@ -10,150 +10,395 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Write};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
io::{stdin, stdout, BufReader, Read, Stdout, Write},
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||
extern crate nix;
|
||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||
use nix::sys::termios::{self, LocalFlags, SetArg};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
extern crate redox_termios;
|
||||
#[cfg(target_os = "redox")]
|
||||
extern crate syscall;
|
||||
use clap::{App, Arg};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
|
||||
execute, queue,
|
||||
style::Attribute,
|
||||
terminal,
|
||||
};
|
||||
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "A file perusal filter for CRT viewing.";
|
||||
|
||||
mod options {
|
||||
pub const FILE: &str = "file";
|
||||
pub mod options {
|
||||
pub const SILENT: &str = "silent";
|
||||
pub const LOGICAL: &str = "logical";
|
||||
pub const NO_PAUSE: &str = "no-pause";
|
||||
pub const PRINT_OVER: &str = "print-over";
|
||||
pub const CLEAN_PRINT: &str = "clean-print";
|
||||
pub const SQUEEZE: &str = "squeeze";
|
||||
pub const PLAIN: &str = "plain";
|
||||
pub const LINES: &str = "lines";
|
||||
pub const NUMBER: &str = "number";
|
||||
pub const PATTERN: &str = "pattern";
|
||||
pub const FROM_LINE: &str = "from-line";
|
||||
pub const FILES: &str = "files";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{} [options] <file>...", executable!())
|
||||
}
|
||||
const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.usage(usage.as_str())
|
||||
.about(ABOUT)
|
||||
.about("A file perusal filter for CRT viewing.")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
// The commented arguments below are unimplemented:
|
||||
/*
|
||||
.arg(
|
||||
Arg::with_name(options::FILE)
|
||||
.number_of_values(1)
|
||||
.multiple(true),
|
||||
Arg::with_name(options::SILENT)
|
||||
.short("d")
|
||||
.long(options::SILENT)
|
||||
.help("Display help instead of ringing bell"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::LOGICAL)
|
||||
.short("f")
|
||||
.long(options::LOGICAL)
|
||||
.help("Count logical rather than screen lines"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::NO_PAUSE)
|
||||
.short("l")
|
||||
.long(options::NO_PAUSE)
|
||||
.help("Suppress pause after form feed"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::PRINT_OVER)
|
||||
.short("c")
|
||||
.long(options::PRINT_OVER)
|
||||
.help("Do not scroll, display text and clean line ends"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::CLEAN_PRINT)
|
||||
.short("p")
|
||||
.long(options::CLEAN_PRINT)
|
||||
.help("Do not scroll, clean screen and display text"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::SQUEEZE)
|
||||
.short("s")
|
||||
.long(options::SQUEEZE)
|
||||
.help("Squeeze multiple blank lines into one"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::PLAIN)
|
||||
.short("u")
|
||||
.long(options::PLAIN)
|
||||
.help("Suppress underlining and bold"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::LINES)
|
||||
.short("n")
|
||||
.long(options::LINES)
|
||||
.value_name("number")
|
||||
.takes_value(true)
|
||||
.help("The number of lines per screenful"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::NUMBER)
|
||||
.allow_hyphen_values(true)
|
||||
.long(options::NUMBER)
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.help("Same as --lines"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::FROM_LINE)
|
||||
.short("F")
|
||||
.allow_hyphen_values(true)
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.value_name("number")
|
||||
.help("Display file beginning from line number"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::PATTERN)
|
||||
.short("P")
|
||||
.allow_hyphen_values(true)
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.help("Display file beginning from pattern match"),
|
||||
)
|
||||
*/
|
||||
.arg(
|
||||
Arg::with_name(options::FILES)
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.help("Path to the files to be read"),
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
// FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input)
|
||||
if let None | Some("-") = matches.value_of(options::FILE) {
|
||||
show_usage_error!("Reading from stdin isn't supported yet.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if let Some(x) = matches.value_of(options::FILE) {
|
||||
let path = std::path::Path::new(x);
|
||||
if path.is_dir() {
|
||||
show_usage_error!("'{}' is a directory.", x);
|
||||
return 1;
|
||||
|
||||
let mut buff = String::new();
|
||||
if let Some(filenames) = matches.values_of(options::FILES) {
|
||||
let mut stdout = setup_term();
|
||||
let length = filenames.len();
|
||||
for (idx, fname) in filenames.enumerate() {
|
||||
let fname = Path::new(fname);
|
||||
if fname.is_dir() {
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
show_usage_error!("'{}' is a directory.", fname.display());
|
||||
return 1;
|
||||
}
|
||||
if !fname.exists() {
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
show_error!(
|
||||
"cannot open {}: No such file or directory",
|
||||
fname.display()
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if length > 1 {
|
||||
buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", fname.to_str().unwrap()));
|
||||
}
|
||||
let mut reader = BufReader::new(File::open(fname).unwrap());
|
||||
reader.read_to_string(&mut buff).unwrap();
|
||||
let is_last = idx + 1 == length;
|
||||
more(&buff, &mut stdout, is_last);
|
||||
buff.clear();
|
||||
}
|
||||
reset_term(&mut stdout);
|
||||
} else if atty::isnt(atty::Stream::Stdin) {
|
||||
stdin().read_to_string(&mut buff).unwrap();
|
||||
let mut stdout = setup_term();
|
||||
more(&buff, &mut stdout, true);
|
||||
reset_term(&mut stdout);
|
||||
} else {
|
||||
show_usage_error!("bad usage");
|
||||
}
|
||||
|
||||
more(matches);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||
fn setup_term() -> termios::Termios {
|
||||
let mut term = termios::tcgetattr(0).unwrap();
|
||||
// Unset canonical mode, so we get characters immediately
|
||||
term.local_flags.remove(LocalFlags::ICANON);
|
||||
// Disable local echo
|
||||
term.local_flags.remove(LocalFlags::ECHO);
|
||||
termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
|
||||
term
|
||||
#[cfg(not(target_os = "fuchsia"))]
|
||||
fn setup_term() -> std::io::Stdout {
|
||||
let stdout = stdout();
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
stdout
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "fuchsia"))]
|
||||
#[cfg(target_os = "fuchsia")]
|
||||
#[inline(always)]
|
||||
fn setup_term() -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn setup_term() -> redox_termios::Termios {
|
||||
let mut term = redox_termios::Termios::default();
|
||||
let fd = syscall::dup(0, b"termios").unwrap();
|
||||
syscall::read(fd, &mut term).unwrap();
|
||||
term.local_flags &= !redox_termios::ICANON;
|
||||
term.local_flags &= !redox_termios::ECHO;
|
||||
syscall::write(fd, &term).unwrap();
|
||||
let _ = syscall::close(fd);
|
||||
term
|
||||
#[cfg(not(target_os = "fuchsia"))]
|
||||
fn reset_term(stdout: &mut std::io::Stdout) {
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
// Clear the prompt
|
||||
queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
|
||||
// Move cursor to the beginning without printing new line
|
||||
print!("\r");
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||
fn reset_term(term: &mut termios::Termios) {
|
||||
term.local_flags.insert(LocalFlags::ICANON);
|
||||
term.local_flags.insert(LocalFlags::ECHO);
|
||||
termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "fuchsia"))]
|
||||
#[cfg(target_os = "fuchsia")]
|
||||
#[inline(always)]
|
||||
fn reset_term(_: &mut usize) {}
|
||||
|
||||
#[cfg(any(target_os = "redox"))]
|
||||
fn reset_term(term: &mut redox_termios::Termios) {
|
||||
let fd = syscall::dup(0, b"termios").unwrap();
|
||||
syscall::read(fd, term).unwrap();
|
||||
term.local_flags |= redox_termios::ICANON;
|
||||
term.local_flags |= redox_termios::ECHO;
|
||||
syscall::write(fd, &term).unwrap();
|
||||
let _ = syscall::close(fd);
|
||||
}
|
||||
fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) {
|
||||
let (cols, rows) = terminal::size().unwrap();
|
||||
let lines = break_buff(buff, usize::from(cols));
|
||||
let line_count: u16 = lines.len().try_into().unwrap();
|
||||
|
||||
fn more(matches: ArgMatches) {
|
||||
let mut f: Box<dyn BufRead> = match matches.value_of(options::FILE) {
|
||||
None | Some("-") => Box::new(BufReader::new(stdin())),
|
||||
Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())),
|
||||
};
|
||||
let mut buffer = [0; 1024];
|
||||
let mut upper_mark = 0;
|
||||
let mut lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||
|
||||
let mut term = setup_term();
|
||||
draw(
|
||||
&mut upper_mark,
|
||||
rows,
|
||||
&mut stdout,
|
||||
lines.clone(),
|
||||
line_count,
|
||||
);
|
||||
|
||||
let mut end = false;
|
||||
while let Ok(sz) = f.read(&mut buffer) {
|
||||
if sz == 0 {
|
||||
break;
|
||||
}
|
||||
stdout().write_all(&buffer[0..sz]).unwrap();
|
||||
for byte in std::io::stdin().bytes() {
|
||||
match byte.unwrap() {
|
||||
b' ' => break,
|
||||
b'q' | 27 => {
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
if end {
|
||||
break;
|
||||
// Specifies whether we have reached the end of the file and should
|
||||
// return on the next keypress. 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 {
|
||||
if event::poll(Duration::from_millis(10)).unwrap() {
|
||||
match event::read().unwrap() {
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('q'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
})
|
||||
| Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}) => {
|
||||
reset_term(&mut stdout);
|
||||
std::process::exit(0);
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Down,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
})
|
||||
| Event::Key(KeyEvent {
|
||||
code: KeyCode::Char(' '),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1));
|
||||
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Up,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1));
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||
draw(
|
||||
&mut upper_mark,
|
||||
rows,
|
||||
&mut stdout,
|
||||
lines.clone(),
|
||||
line_count,
|
||||
);
|
||||
|
||||
reset_term(&mut term);
|
||||
println!();
|
||||
if lines_left == 0 {
|
||||
if to_be_done || is_last {
|
||||
return
|
||||
}
|
||||
to_be_done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
upper_mark: &mut u16,
|
||||
rows: u16,
|
||||
mut stdout: &mut std::io::Stdout,
|
||||
lines: Vec<String>,
|
||||
lc: u16,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
make_prompt_and_flush(&mut stdout, lower_mark, lc);
|
||||
*upper_mark = up_mark;
|
||||
}
|
||||
|
||||
// Break the lines on the cols of the terminal
|
||||
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for l in buff.lines() {
|
||||
lines.append(&mut break_line(l, cols));
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
fn break_line(mut line: &str, cols: usize) -> Vec<String> {
|
||||
let breaks = (line.len() / cols).saturating_add(1);
|
||||
let mut lines = Vec::with_capacity(breaks);
|
||||
// TODO: Use unicode width instead of the length in bytes.
|
||||
if line.len() < cols {
|
||||
lines.push(line.to_string());
|
||||
return lines;
|
||||
}
|
||||
|
||||
for _ in 1..=breaks {
|
||||
let (line1, line2) = line.split_at(cols);
|
||||
lines.push(line1.to_string());
|
||||
if line2.len() < cols {
|
||||
lines.push(line2.to_string());
|
||||
break;
|
||||
}
|
||||
line = line2;
|
||||
}
|
||||
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);
|
||||
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) {
|
||||
write!(
|
||||
stdout,
|
||||
"\r{}--More--({}%){}",
|
||||
Attribute::Reverse,
|
||||
((lower_mark as f64 / lc as f64) * 100.0).round() as u16,
|
||||
Attribute::Reset
|
||||
)
|
||||
.unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{break_line, calc_range};
|
||||
|
||||
// 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!((75, 100), calc_range(85, 25, 100));
|
||||
}
|
||||
#[test]
|
||||
fn test_break_lines_long() {
|
||||
let mut test_string = String::with_capacity(100);
|
||||
for _ in 0..200 {
|
||||
test_string.push('#');
|
||||
}
|
||||
|
||||
let lines = break_line(&test_string, 80);
|
||||
|
||||
assert_eq!(
|
||||
(80, 80, 40),
|
||||
(lines[0].len(), lines[1].len(), lines[2].len())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_break_lines_short() {
|
||||
let mut test_string = String::with_capacity(100);
|
||||
for _ in 0..20 {
|
||||
test_string.push('#');
|
||||
}
|
||||
|
||||
let lines = break_line(&test_string, 80);
|
||||
|
||||
assert_eq!(20, lines[0].len());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,22 @@ use crate::common::util::*;
|
|||
|
||||
#[test]
|
||||
fn test_more_no_arg() {
|
||||
// stderr = more: Reading from stdin isn't supported yet.
|
||||
new_ucmd!().fails();
|
||||
// Reading from stdin is now supported, so this must succeed
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
new_ucmd!().succeeds();
|
||||
} else {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_more_dir_arg() {
|
||||
let result = new_ucmd!().arg(".").run();
|
||||
result.failure();
|
||||
const EXPECTED_ERROR_MESSAGE: &str =
|
||||
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
||||
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
|
||||
// Run the test only if there's a valud terminal, else do nothing
|
||||
// Maybe we could capture the error, i.e. "Device not found" in that case
|
||||
// but I am leaving this for later
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
let result = new_ucmd!().arg(".").run();
|
||||
result.failure();
|
||||
const EXPECTED_ERROR_MESSAGE: &str =
|
||||
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
||||
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
|
||||
} else {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue