diff --git a/Cargo.lock b/Cargo.lock index f1483f2..e0092a7 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" @@ -109,10 +111,13 @@ version = "0.1.4" dependencies = [ "ansi_term", "argparse", + "atty", "colored", + "libc", "serde_json", "ureq", "webbrowser", + "windows-sys", ] [[package]] @@ -132,9 +137,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" @@ -455,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 fcd2d24..292edae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,17 @@ 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" 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" [features] diff --git a/src/main.rs b/src/main.rs index 3b8ec9d..efc4b93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ use std::{ io::{stdin, stdout, Write}, + process::{Command, Stdio}, thread::{self, JoinHandle}, + env, }; use argparse::{ArgumentParser, List, Print, Store, StoreTrue}; use colored::*; use serde_json::Value; +use atty::Stream; macro_rules! JISHO_URL { () => { @@ -24,7 +27,7 @@ struct Options { impl Default for Options { fn default() -> Self { Self { - limit: 4, + limit: 0, query: String::default(), kanji: false, interactive: false, @@ -33,6 +36,14 @@ impl Default for Options { } fn main() -> Result<(), ureq::Error> { + let term_size; + + if atty::is(Stream::Stdout) { + term_size = terminal_size().unwrap_or(0); + } else { + term_size = 0; + } + let options = parse_args(); let mut query = { @@ -50,6 +61,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 @@ -91,15 +105,30 @@ fn main() -> Result<(), ureq::Error> { // 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(); + 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 + env::set_var("CLICOLOR_FORCE", "1"); + pipe_to_less(output); + } else { + print!("{}", output); } if !options.interactive { @@ -119,7 +148,7 @@ 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 japanese = value_to_arr(value.get("japanese")?).get(0)?.to_owned(); let reading = japanese @@ -129,20 +158,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)); + let mut 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 +335,73 @@ fn parse_args() -> Options { ap.parse_args_or_exit(); } - if options.limit == 0 { - options.limit = 1; - } - options.query = query_vec.join(" "); 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}; + + 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) + } + } +} + +#[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) + } + } +}