mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
Rewrite the cli usage function
Add crossterm as dependency Complete the paging portion Fixed tests cp: extract linux COW logic into function cp: add --reflink support for macOS Fixes #1773 Fix error in Cargo.lock Quit automatically if not much output is left Remove unnecessary redox and windows specific code Handle line wrapping Put everything according to uutils coding standards Add support for multiple files Fix failing test Use the args argument to get cli arguments Fix bug where text is repeated multiple times during printing Add a little prompt Add a top file prompt for multiple files Change println in loops to stdout.write and setup terminal only once Fix bug where all lines were printed in a single row Remove useless file and fix failing test Fix another test
This commit is contained in:
parent
33139817a2
commit
2593b3f2e1
5 changed files with 494 additions and 119 deletions
121
Cargo.lock
generated
121
Cargo.lock
generated
|
@ -217,6 +217,7 @@ dependencies = [
|
||||||
name = "coreutils"
|
name = "coreutils"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"atty",
|
||||||
"conv",
|
"conv",
|
||||||
"filetime",
|
"filetime",
|
||||||
"glob 0.3.0",
|
"glob 0.3.0",
|
||||||
|
@ -507,6 +508,31 @@ dependencies = [
|
||||||
"lazy_static",
|
"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]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.1.6"
|
version = "1.1.6"
|
||||||
|
@ -732,6 +758,15 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
|
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]]
|
[[package]]
|
||||||
name = "ioctl-sys"
|
name = "ioctl-sys"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -802,6 +837,15 @@ version = "0.2.85"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
|
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -862,6 +906,28 @@ dependencies = [
|
||||||
"autocfg",
|
"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]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
@ -893,6 +959,15 @@ version = "0.1.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
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]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
@ -977,6 +1052,31 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"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.6",
|
||||||
|
"smallvec 1.6.1",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
@ -1417,6 +1517,26 @@ dependencies = [
|
||||||
"generic-array",
|
"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]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "0.6.14"
|
version = "0.6.14"
|
||||||
|
@ -2112,6 +2232,7 @@ name = "uu_more"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"crossterm",
|
||||||
"nix 0.13.1",
|
"nix 0.13.1",
|
||||||
"redox_syscall 0.1.57",
|
"redox_syscall 0.1.57",
|
||||||
"redox_termios",
|
"redox_termios",
|
||||||
|
|
|
@ -347,6 +347,7 @@ unindent = "0.1"
|
||||||
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
|
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
|
atty = "*"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
rust-users = { version="0.10", package="users" }
|
rust-users = { version="0.10", package="users" }
|
||||||
|
|
|
@ -18,6 +18,7 @@ path = "src/more.rs"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
|
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
|
||||||
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
|
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
|
||||||
|
crossterm = ">=0.19"
|
||||||
|
|
||||||
[target.'cfg(target_os = "redox")'.dependencies]
|
[target.'cfg(target_os = "redox")'.dependencies]
|
||||||
redox_termios = "0.1"
|
redox_termios = "0.1"
|
||||||
|
|
|
@ -10,150 +10,395 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::{
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, Read, Write};
|
convert::TryInto,
|
||||||
|
fs::File,
|
||||||
|
io::{stdin, stdout, BufReader, Read, Stdout, Write},
|
||||||
|
path::Path,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
||||||
extern crate nix;
|
extern crate nix;
|
||||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
|
||||||
use nix::sys::termios::{self, LocalFlags, SetArg};
|
|
||||||
use uucore::InvalidEncodingHandling;
|
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
use clap::{App, Arg};
|
||||||
extern crate redox_termios;
|
use crossterm::{
|
||||||
#[cfg(target_os = "redox")]
|
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
|
||||||
extern crate syscall;
|
execute,
|
||||||
|
style::Attribute,
|
||||||
|
terminal,
|
||||||
|
};
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches};
|
pub mod options {
|
||||||
|
pub const SILENT: &str = "silent";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const LOGICAL: &str = "logical";
|
||||||
static ABOUT: &str = "A file perusal filter for CRT viewing.";
|
pub const NO_PAUSE: &str = "no-pause";
|
||||||
|
pub const PRINT_OVER: &str = "print-over";
|
||||||
mod options {
|
pub const CLEAN_PRINT: &str = "clean-print";
|
||||||
pub const FILE: &str = "file";
|
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 {
|
const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";
|
||||||
format!("{} [options] <file>...", executable!())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
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!())
|
let matches = App::new(executable!())
|
||||||
.version(VERSION)
|
.about("A file perusal filter for CRT viewing.")
|
||||||
.usage(usage.as_str())
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about(ABOUT)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(options::FILE)
|
Arg::with_name(options::SILENT)
|
||||||
.number_of_values(1)
|
.short("d")
|
||||||
.multiple(true),
|
.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);
|
.get_matches_from(args);
|
||||||
|
let mut buff = String::new();
|
||||||
|
let mut stdout = setup_term();
|
||||||
|
|
||||||
// FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input)
|
if let Some(filenames) = matches.values_of(options::FILES) {
|
||||||
if let None | Some("-") = matches.value_of(options::FILE) {
|
let length = filenames.len();
|
||||||
show_usage_error!("Reading from stdin isn't supported yet.");
|
for (idx, fname) in filenames.clone().enumerate() {
|
||||||
|
if Path::new(fname).is_dir() {
|
||||||
|
show_usage_error!("'{}' is a directory.", fname);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if filenames.len() > 1 {
|
||||||
if let Some(x) = matches.value_of(options::FILE) {
|
buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", fname));
|
||||||
let path = std::path::Path::new(x);
|
|
||||||
if path.is_dir() {
|
|
||||||
show_usage_error!("'{}' is a directory.", x);
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
let mut reader = BufReader::new(File::open(fname).unwrap());
|
||||||
|
reader.read_to_string(&mut buff).unwrap();
|
||||||
|
let last = idx + 1 == length;
|
||||||
|
more(&buff, &mut stdout, last);
|
||||||
|
buff.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stdin().read_to_string(&mut buff).unwrap();
|
||||||
|
more(&buff, &mut stdout, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
more(matches);
|
|
||||||
|
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
#[cfg(not(target_os = "fuchsia"))]
|
||||||
fn setup_term() -> termios::Termios {
|
fn setup_term() -> std::io::Stdout {
|
||||||
let mut term = termios::tcgetattr(0).unwrap();
|
let mut stdout = stdout();
|
||||||
// Unset canonical mode, so we get characters immediately
|
terminal::enable_raw_mode().unwrap();
|
||||||
term.local_flags.remove(LocalFlags::ICANON);
|
// Change this to a queue if more commands are executed to avoid too many writes to the terminal
|
||||||
// Disable local echo
|
execute!(stdout, terminal::Clear(terminal::ClearType::All)).unwrap();
|
||||||
term.local_flags.remove(LocalFlags::ECHO);
|
stdout
|
||||||
termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
|
|
||||||
term
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(windows, target_os = "fuchsia"))]
|
#[cfg(target_os = "fuchsia")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn setup_term() -> usize {
|
fn setup_term() -> usize {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(not(target_os = "fuchsia"))]
|
||||||
fn setup_term() -> redox_termios::Termios {
|
fn reset_term(stdout: &mut std::io::Stdout) {
|
||||||
let mut term = redox_termios::Termios::default();
|
terminal::disable_raw_mode().unwrap();
|
||||||
let fd = syscall::dup(0, b"termios").unwrap();
|
// Clear the prompt
|
||||||
syscall::read(fd, &mut term).unwrap();
|
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
|
||||||
term.local_flags &= !redox_termios::ICANON;
|
println!("\r");
|
||||||
term.local_flags &= !redox_termios::ECHO;
|
|
||||||
syscall::write(fd, &term).unwrap();
|
|
||||||
let _ = syscall::close(fd);
|
|
||||||
term
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "fuchsia")))]
|
#[cfg(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"))]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn reset_term(_: &mut usize) {}
|
fn reset_term(_: &mut usize) {}
|
||||||
|
|
||||||
#[cfg(any(target_os = "redox"))]
|
fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) {
|
||||||
fn reset_term(term: &mut redox_termios::Termios) {
|
let (cols, rows) = terminal::size().unwrap();
|
||||||
let fd = syscall::dup(0, b"termios").unwrap();
|
let lines = break_buff(buff, usize::from(cols));
|
||||||
syscall::read(fd, term).unwrap();
|
let line_count: u16 = lines.len().try_into().unwrap();
|
||||||
term.local_flags |= redox_termios::ICANON;
|
|
||||||
term.local_flags |= redox_termios::ECHO;
|
// Print everything and quit if line count is less than the availale rows
|
||||||
syscall::write(fd, &term).unwrap();
|
if line_count < rows {
|
||||||
let _ = syscall::close(fd);
|
println!("{}", buff);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut upper_mark = 0;
|
||||||
|
// Number of lines left
|
||||||
|
let mut lines_left = line_count - (upper_mark + rows);
|
||||||
|
// Are we on the very last page and the next down arrow will return this function
|
||||||
|
let mut to_be_done = false;
|
||||||
|
draw(
|
||||||
|
&mut upper_mark,
|
||||||
|
rows,
|
||||||
|
&mut stdout,
|
||||||
|
lines.clone(),
|
||||||
|
line_count,
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
}) => {
|
||||||
|
// If this is the last page but some text
|
||||||
|
// is still left that does not require a page of its own
|
||||||
|
// then this event will print the left lines
|
||||||
|
if lines_left < rows && !to_be_done {
|
||||||
|
for l in lines.iter().skip((line_count - upper_mark - rows).into()) {
|
||||||
|
stdout.write_all(format!("\r{}\n", l).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
make_prompt_and_flush(&mut stdout, line_count, line_count);
|
||||||
|
// If this is not the last input file
|
||||||
|
// do not return, but the next down arrow must return
|
||||||
|
// because we have printed everyhing
|
||||||
|
if !is_last {
|
||||||
|
to_be_done = true;
|
||||||
|
} else {
|
||||||
|
// Else quit
|
||||||
|
reset_term(&mut stdout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This handles the next arrow key to quit
|
||||||
|
} else if lines_left < rows && to_be_done {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Print a normal page
|
||||||
|
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1));
|
||||||
|
lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||||
|
draw(
|
||||||
|
&mut upper_mark,
|
||||||
|
rows,
|
||||||
|
&mut stdout,
|
||||||
|
lines.clone(),
|
||||||
|
line_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Up,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => {
|
||||||
|
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1));
|
||||||
|
draw(
|
||||||
|
&mut upper_mark,
|
||||||
|
rows,
|
||||||
|
&mut stdout,
|
||||||
|
lines.clone(),
|
||||||
|
line_count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn more(matches: ArgMatches) {
|
fn draw(
|
||||||
let mut f: Box<dyn BufRead> = match matches.value_of(options::FILE) {
|
upper_mark: &mut u16,
|
||||||
None | Some("-") => Box::new(BufReader::new(stdin())),
|
rows: u16,
|
||||||
Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())),
|
mut stdout: &mut std::io::Stdout,
|
||||||
};
|
lines: Vec<String>,
|
||||||
let mut buffer = [0; 1024];
|
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)));
|
||||||
|
|
||||||
let mut term = setup_term();
|
for line in displayed_lines {
|
||||||
|
stdout
|
||||||
let mut end = false;
|
.write_all(format!("\r{}\n", line).as_bytes())
|
||||||
while let Ok(sz) = f.read(&mut buffer) {
|
.unwrap();
|
||||||
if sz == 0 {
|
}
|
||||||
break;
|
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);
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_term(&mut term);
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,22 @@ use crate::common::util::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_more_no_arg() {
|
fn test_more_no_arg() {
|
||||||
// stderr = more: Reading from stdin isn't supported yet.
|
// Reading from stdin is now supported, so this must succeed
|
||||||
new_ucmd!().fails();
|
if atty::is(atty::Stream::Stdout) {
|
||||||
|
new_ucmd!().succeeds();
|
||||||
|
} else {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_more_dir_arg() {
|
fn test_more_dir_arg() {
|
||||||
|
// 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();
|
let result = new_ucmd!().arg(".").run();
|
||||||
result.failure();
|
result.failure();
|
||||||
const EXPECTED_ERROR_MESSAGE: &str =
|
const EXPECTED_ERROR_MESSAGE: &str =
|
||||||
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
"more: '.' is a directory.\nTry 'more --help' for more information.";
|
||||||
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
|
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
|
||||||
|
} else {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue