From 7f149998bb1c5a5637c1e83d81950cd7202007ad Mon Sep 17 00:00:00 2001 From: Hiers Date: Fri, 17 Feb 2023 10:23:52 +0000 Subject: [PATCH 1/4] Print to less if output is bigger than terminal window height. --- Cargo.lock | 40 ++++++++++++++++++++++++++++-- Cargo.toml | 3 +++ src/main.rs | 71 +++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 96 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1483f2..980e034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" version = "0.12.1" @@ -67,6 +69,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -109,7 +132,10 @@ version = "0.1.4" dependencies = [ "ansi_term", "argparse", + "atty", "colored", + "libc", + "pager", "serde_json", "ureq", "webbrowser", @@ -132,9 +158,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "log" @@ -157,6 +183,16 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +[[package]] +name = "pager" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2599211a5c97fbbb1061d3dc751fa15f404927e4846e07c643287d6d1f462880" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "percent-encoding" version = "2.1.0" diff --git a/Cargo.toml b/Cargo.toml index fcd2d24..e430d6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,13 @@ edition = "2018" [dependencies] ureq = { version = "2.1.0", features = ["json"] } +libc = "0.2.139" serde_json = "1.0.64" ansi_term = "0.12.1" colored = "2.0.0" argparse = "0.2.2" webbrowser = "0.5.5" +atty = "0.2" +pager = "0.16.1" [features] diff --git a/src/main.rs b/src/main.rs index 3b8ec9d..504e560 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,15 @@ use std::{ io::{stdin, stdout, Write}, thread::{self, JoinHandle}, + env, }; +use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; use argparse::{ArgumentParser, List, Print, Store, StoreTrue}; use colored::*; use serde_json::Value; +use atty::Stream; +use pager::Pager; macro_rules! JISHO_URL { () => { @@ -24,7 +28,7 @@ struct Options { impl Default for Options { fn default() -> Self { Self { - limit: 4, + limit: 0, query: String::default(), kanji: false, interactive: false, @@ -33,6 +37,18 @@ impl Default for Options { } fn main() -> Result<(), ureq::Error> { + let term_size; + + if atty::is(Stream::Stdout) { + match terminal_size() { + Ok(s) => term_size = s, + Err(_e) => term_size = 0 + } + } else { + term_size = 0; + } + + let mut lines_output = 0; let options = parse_args(); let mut query = { @@ -89,17 +105,30 @@ fn main() -> Result<(), ureq::Error> { println!(); } + let mut output = String::new(); // Iterate over meanings and print them for (i, entry) in body.iter().enumerate() { - if i >= options.limit { + if i >= options.limit && options.limit != 0 { break; } - - if print_item(&query, entry).is_some() && i + 2 <= options.limit { - println!(); + match print_item(&query, entry, &mut output) { + Some(r) => lines_output += r, + None => continue, } + + output.push('\n'); + lines_output += 1; } - println!(); + output.pop(); + lines_output -= 1; + + if lines_output >= term_size - 1 && term_size != 0{ + /* output is a different process that is not a tty (i.e. less), but we want to keep colour */ + env::set_var("CLICOLOR_FORCE", "1"); + Pager::with_pager("less -R").setup(); + } + print!("{}", output); + } if !options.interactive { @@ -119,7 +148,8 @@ fn main() -> Result<(), ureq::Error> { Ok(()) } -fn print_item(query: &str, value: &Value) -> Option<()> { +fn print_item(query: &str, value: &Value, output: &mut String) -> Option { + let mut aux; let japanese = value_to_arr(value.get("japanese")?).get(0)?.to_owned(); let reading = japanese @@ -129,20 +159,22 @@ fn print_item(query: &str, value: &Value) -> Option<()> { let word = value_to_str(japanese.get("word").unwrap_or(japanese.get("reading")?)); - println!("{}[{}] {}", word, reading, format_result_tags(value)); + aux = format!("{}[{}] {}\n", word, reading, format_result_tags(value)); + *output += &aux; // Print senses - let senses = value.get("senses")?; - for (i, sense) in value_to_arr(senses).iter().enumerate() { + let senses = value_to_arr(value.get("senses")?); + for (i, sense) in senses.iter().enumerate() { let sense_str = format_sense(&sense, i); if sense_str.is_empty() { continue; } - println!(" {}", sense_str); + aux = format!(" {}\n", sense_str); + *output += &aux; } - Some(()) + Some(senses.iter().count() + 1) } fn format_sense(value: &Value, index: usize) -> String { @@ -304,10 +336,17 @@ fn parse_args() -> Options { ap.parse_args_or_exit(); } - if options.limit == 0 { - options.limit = 1; - } - options.query = query_vec.join(" "); options } + +fn terminal_size() -> Result { + unsafe { + let mut size: c_ushort = 0; + if ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _) != 0 { + Err(-1) + } else { + Ok(size as usize) + } + } +} From bc8a29fcf385b91d0cf6e156fb002776ca9c31a4 Mon Sep 17 00:00:00 2001 From: Hiers Date: Fri, 17 Feb 2023 11:41:38 +0000 Subject: [PATCH 2/4] Fix buffer underflow bug in output line counter. --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 504e560..3bc605e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,9 @@ fn main() -> Result<(), ureq::Error> { lines_output += 1; } output.pop(); - lines_output -= 1; + if lines_output > 0 { + lines_output -= 1; + } if lines_output >= term_size - 1 && term_size != 0{ /* output is a different process that is not a tty (i.e. less), but we want to keep colour */ From d56d31cc6427ed124f19fd26c6a15e5bd645a1d4 Mon Sep 17 00:00:00 2001 From: Hiers Date: Sun, 19 Feb 2023 11:01:24 +0000 Subject: [PATCH 3/4] Removed the pager crate in favour of implementing that functionality in jisho-cli. Added windows version of terminal_size function. --- Cargo.lock | 99 ++++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 4 ++- src/main.rs | 70 +++++++++++++++++++++++++++++++------ 3 files changed, 130 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 980e034..e0092a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,27 +69,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -135,10 +114,10 @@ dependencies = [ "atty", "colored", "libc", - "pager", "serde_json", "ureq", "webbrowser", + "windows-sys", ] [[package]] @@ -183,16 +162,6 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" -[[package]] -name = "pager" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2599211a5c97fbbb1061d3dc751fa15f404927e4846e07c643287d6d1f462880" -dependencies = [ - "errno", - "libc", -] - [[package]] name = "percent-encoding" version = "2.1.0" @@ -491,3 +460,69 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" diff --git a/Cargo.toml b/Cargo.toml index e430d6a..292edae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ repository = "https://github.com/JojiiOfficial/jisho-cli" license = "GPL-3.0" edition = "2018" +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.45.0", features = ["Win32_Foundation", "Win32_System_Console"] } + [dependencies] ureq = { version = "2.1.0", features = ["json"] } libc = "0.2.139" @@ -17,6 +20,5 @@ colored = "2.0.0" argparse = "0.2.2" webbrowser = "0.5.5" atty = "0.2" -pager = "0.16.1" [features] diff --git a/src/main.rs b/src/main.rs index 3bc605e..7f27157 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,14 @@ use std::{ io::{stdin, stdout, Write}, + process::{Command, Stdio}, thread::{self, JoinHandle}, env, }; -use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; use argparse::{ArgumentParser, List, Print, Store, StoreTrue}; use colored::*; use serde_json::Value; use atty::Stream; -use pager::Pager; macro_rules! JISHO_URL { () => { @@ -48,7 +47,6 @@ fn main() -> Result<(), ureq::Error> { term_size = 0; } - let mut lines_output = 0; let options = parse_args(); let mut query = { @@ -66,6 +64,9 @@ fn main() -> Result<(), ureq::Error> { }; loop { + let mut lines_output = 0; + let mut output = String::new(); + if options.kanji { // Open kanji page here let threads = query @@ -105,7 +106,6 @@ fn main() -> Result<(), ureq::Error> { println!(); } - let mut output = String::new(); // Iterate over meanings and print them for (i, entry) in body.iter().enumerate() { if i >= options.limit && options.limit != 0 { @@ -124,13 +124,32 @@ fn main() -> Result<(), ureq::Error> { lines_output -= 1; } - if lines_output >= term_size - 1 && term_size != 0{ - /* output is a different process that is not a tty (i.e. less), but we want to keep colour */ - env::set_var("CLICOLOR_FORCE", "1"); - Pager::with_pager("less -R").setup(); - } - print!("{}", output); + } + if lines_output >= term_size - 1 && term_size != 0 { + // Output is a different process that is not a tty (i.e. less), but we want to keep colour + env::set_var("CLICOLOR_FORCE", "1"); + + match Command::new("less") + .arg("-R") + .stdin(Stdio::piped()) + .spawn() { + Ok(mut process) => { + if let Err(e) = process.stdin.as_ref().unwrap().write_all(output.as_bytes()) { + panic!("couldn't pipe to less: {}", e); + } + + // We don't care about the return value, only whether wait failed or not + if process.wait().is_err() { + panic!("wait() was called on non-existent child process\ + - this should not be possible"); + } + } + // less not found in PATH; print normally + Err(_e) => print!("{}", output) + }; + } else { + print!("{}", output); } if !options.interactive { @@ -342,7 +361,10 @@ fn parse_args() -> Options { options } +#[cfg(unix)] fn terminal_size() -> Result { + use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; + unsafe { let mut size: c_ushort = 0; if ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _) != 0 { @@ -352,3 +374,31 @@ fn terminal_size() -> Result { } } } + +#[cfg(windows)] +fn terminal_size() -> Result { + use windows_sys::Win32::System::Console::*; + + unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE) as windows_sys::Win32::Foundation::HANDLE; + + // Unlike the linux function, rust will complain if only part of the struct is sent + let mut window = CONSOLE_SCREEN_BUFFER_INFO { + dwSize: COORD { X: 0, Y: 0}, + dwCursorPosition: COORD { X: 0, Y: 0}, + wAttributes: 0, + dwMaximumWindowSize: COORD {X: 0, Y: 0}, + srWindow: SMALL_RECT { + Top: 0, + Bottom: 0, + Left: 0, + Right: 0 + } + }; + if GetConsoleScreenBufferInfo(handle, &mut window) == 0 { + Err(0) + } else { + Ok((window.srWindow.Bottom - window.srWindow.Top) as usize) + } + } +} From 1c3c5644d3d7ec81f7a00e53dc5500a58d2dc9ec Mon Sep 17 00:00:00 2001 From: Hiers Date: Sun, 19 Feb 2023 23:18:42 +0000 Subject: [PATCH 4/4] Move child process code to separate function and other small changes. --- src/main.rs | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7f27157..efc4b93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,10 +39,7 @@ fn main() -> Result<(), ureq::Error> { let term_size; if atty::is(Stream::Stdout) { - match terminal_size() { - Ok(s) => term_size = s, - Err(_e) => term_size = 0 - } + term_size = terminal_size().unwrap_or(0); } else { term_size = 0; } @@ -129,25 +126,7 @@ fn main() -> Result<(), ureq::Error> { if lines_output >= term_size - 1 && term_size != 0 { // Output is a different process that is not a tty (i.e. less), but we want to keep colour env::set_var("CLICOLOR_FORCE", "1"); - - match Command::new("less") - .arg("-R") - .stdin(Stdio::piped()) - .spawn() { - Ok(mut process) => { - if let Err(e) = process.stdin.as_ref().unwrap().write_all(output.as_bytes()) { - panic!("couldn't pipe to less: {}", e); - } - - // We don't care about the return value, only whether wait failed or not - if process.wait().is_err() { - panic!("wait() was called on non-existent child process\ - - this should not be possible"); - } - } - // less not found in PATH; print normally - Err(_e) => print!("{}", output) - }; + pipe_to_less(output); } else { print!("{}", output); } @@ -170,7 +149,6 @@ fn main() -> Result<(), ureq::Error> { } fn print_item(query: &str, value: &Value, output: &mut String) -> Option { - let mut aux; let japanese = value_to_arr(value.get("japanese")?).get(0)?.to_owned(); let reading = japanese @@ -180,7 +158,7 @@ fn print_item(query: &str, value: &Value, output: &mut String) -> Option let word = value_to_str(japanese.get("word").unwrap_or(japanese.get("reading")?)); - aux = format!("{}[{}] {}\n", word, reading, format_result_tags(value)); + let mut aux = format!("{}[{}] {}\n", word, reading, format_result_tags(value)); *output += &aux; // Print senses @@ -361,6 +339,31 @@ fn parse_args() -> Options { options } +fn pipe_to_less(output: String) { + + let command = Command::new("less") + .arg("-R") + .stdin(Stdio::piped()) + .spawn(); + + match command { + Ok(mut process) => { + if let Err(e) = process.stdin.as_ref().unwrap().write_all(output.as_bytes()) { + panic!("couldn't pipe to less: {}", e); + } + + // We don't care about the return value, only whether wait failed or not + if process.wait().is_err() { + panic!("wait() was called on non-existent child process\ + - this should not be possible"); + } + } + + // less not found in PATH; print normally + Err(_e) => print!("{}", output) + }; +} + #[cfg(unix)] fn terminal_size() -> Result { use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ};