mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 20:17:45 +00:00
commit
6141fdcc73
5 changed files with 523 additions and 118 deletions
152
Cargo.lock
generated
152
Cargo.lock
generated
|
@ -224,6 +224,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",
|
||||||
|
@ -487,6 +488,53 @@ 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]]
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "ctor"
|
name = "ctor"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
|
@ -713,6 +761,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"
|
||||||
|
@ -768,6 +825,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -828,6 +894,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"
|
||||||
|
@ -859,6 +947,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-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -971,6 +1068,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.8",
|
||||||
|
"smallvec 1.6.1",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
|
@ -1372,6 +1494,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"
|
||||||
|
@ -1381,6 +1523,12 @@ dependencies = [
|
||||||
"maybe-uninit",
|
"maybe-uninit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.3.19"
|
version = "0.3.19"
|
||||||
|
@ -1840,7 +1988,7 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"smallvec",
|
"smallvec 0.6.14",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2067,7 +2215,9 @@ dependencies = [
|
||||||
name = "uu_more"
|
name = "uu_more"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"atty",
|
||||||
"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",
|
||||||
|
|
|
@ -348,6 +348,7 @@ time = "0.1"
|
||||||
unindent = "0.1"
|
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"
|
||||||
|
atty = "0.2.14"
|
||||||
|
|
||||||
[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,8 @@ 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"
|
||||||
|
atty = "0.2.14"
|
||||||
|
|
||||||
[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, queue,
|
||||||
|
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)
|
// The commented arguments below are unimplemented:
|
||||||
|
/*
|
||||||
.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);
|
||||||
|
|
||||||
// FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input)
|
let mut buff = String::new();
|
||||||
if let None | Some("-") = matches.value_of(options::FILE) {
|
if let Some(filenames) = matches.values_of(options::FILES) {
|
||||||
show_usage_error!("Reading from stdin isn't supported yet.");
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if !fname.exists() {
|
||||||
if let Some(x) = matches.value_of(options::FILE) {
|
terminal::disable_raw_mode().unwrap();
|
||||||
let path = std::path::Path::new(x);
|
show_error!(
|
||||||
if path.is_dir() {
|
"cannot open {}: No such file or directory",
|
||||||
show_usage_error!("'{}' is a directory.", x);
|
fname.display()
|
||||||
|
);
|
||||||
return 1;
|
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
|
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 stdout = stdout();
|
||||||
// Unset canonical mode, so we get characters immediately
|
terminal::enable_raw_mode().unwrap();
|
||||||
term.local_flags.remove(LocalFlags::ICANON);
|
stdout
|
||||||
// Disable local echo
|
|
||||||
term.local_flags.remove(LocalFlags::ECHO);
|
|
||||||
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();
|
queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
|
||||||
term.local_flags &= !redox_termios::ICANON;
|
// Move cursor to the beginning without printing new line
|
||||||
term.local_flags &= !redox_termios::ECHO;
|
print!("\r");
|
||||||
syscall::write(fd, &term).unwrap();
|
stdout.flush().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;
|
let mut upper_mark = 0;
|
||||||
syscall::write(fd, &term).unwrap();
|
let mut lines_left = line_count.saturating_sub(upper_mark + rows);
|
||||||
let _ = syscall::close(fd);
|
|
||||||
|
draw(
|
||||||
|
&mut upper_mark,
|
||||||
|
rows,
|
||||||
|
&mut stdout,
|
||||||
|
lines.clone(),
|
||||||
|
line_count,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
|
||||||
|
if lines_left == 0 {
|
||||||
|
if to_be_done || is_last {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to_be_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
// 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());
|
||||||
}
|
}
|
||||||
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