diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc74f9..eced25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ New features / changes: node, rather than moving down a line. (Functionality was previous available via `i`, but was undocumented; `i` has become unmapped.) +Bug Fixes: +- [Issue #7 / PR #32]: Fix issue with rustyline always reading from + STDIN preventing `/` command from working when input provided via + STDIN. + Internal: - [PR #17]: Upgrade from structopt to clap v3 diff --git a/Cargo.lock b/Cargo.lock index 79dc4ef..92fa9b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,7 @@ dependencies = [ "isatty", "lazy_static", "libc", + "libc-stdhandle", "logos", "regex", "rustyline", @@ -236,6 +237,16 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" +[[package]] +name = "libc-stdhandle" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dac2473dc28934c5e0b82250dab231c9d3b94160d91fe9ff483323b05797551" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "log" version = "0.4.14" diff --git a/Cargo.toml b/Cargo.toml index 073f791..b0c948e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ signal-hook = "0.3.8" libc = "0.2" clap = { version = "3.0", features = ["derive"] } isatty = "0.1" +libc-stdhandle = "0.1.0" diff --git a/src/input.rs b/src/input.rs index cbb5639..065ac9b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,9 +2,8 @@ use signal_hook::consts::SIGWINCH; use signal_hook::low_level::pipe; use termion::event::{parse_event, Event, Key, MouseEvent}; -use std::fs::File; use std::io; -use std::io::Read; +use std::io::{stdin, Read, Stdin}; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; @@ -15,13 +14,29 @@ const BUFFER_SIZE: usize = 1024; const ESCAPE: u8 = 0o33; pub fn get_input() -> impl Iterator> { - let tty = File::open("/dev/tty").unwrap(); + // The readline library we use, rustyline, always gets its input from STDIN. + // If jless accepts its input from STDIN, then rustyline can't accept input. + // To fix this, we open up /dev/tty, and remap it to STDIN, as suggested in + // this StackOverflow post: + // + // https://stackoverflow.com/questions/29689034/piped-stdin-and-keyboard-same-time-in-c + // + // rustyline may add its own fix to support reading from /dev/tty: + // + // https://github.com/kkawakam/rustyline/issues/599 + unsafe { + // freopen(3) docs: https://linux.die.net/man/3/freopen + let filename = std::ffi::CString::new("/dev/tty").unwrap(); + let path = std::ffi::CString::new("r").unwrap(); + let _ = libc::freopen(filename.as_ptr(), path.as_ptr(), libc_stdhandle::stdin()); + } + let (sigwinch_read, sigwinch_write) = UnixStream::pair().unwrap(); pipe::register(SIGWINCH, sigwinch_write).unwrap(); - TuiInput::new(tty, sigwinch_read) + TuiInput::new(stdin(), sigwinch_read) } -fn read_and_retry_on_interrupt(input: &mut File, buf: &mut [u8]) -> io::Result { +fn read_and_retry_on_interrupt(input: &mut Stdin, buf: &mut [u8]) -> io::Result { loop { match input.read(buf) { res @ Ok(_) => { @@ -38,7 +53,7 @@ fn read_and_retry_on_interrupt(input: &mut File, buf: &mut [u8]) -> io::Result { - input: File, + input: Stdin, buffer: [u8; N], buffer_size: usize, buffer_index: usize, @@ -46,7 +61,7 @@ struct BufferedInput { } impl BufferedInput { - fn new(input: File) -> BufferedInput { + fn new(input: Stdin) -> BufferedInput { BufferedInput { input, buffer: [0; N], @@ -136,7 +151,7 @@ struct TuiInput { } impl TuiInput { - fn new(input: File, sigwinch_pipe: UnixStream) -> TuiInput { + fn new(input: Stdin, sigwinch_pipe: UnixStream) -> TuiInput { let sigwinch_fd = sigwinch_pipe.as_raw_fd(); let stdin_fd = input.as_raw_fd(); diff --git a/src/main.rs b/src/main.rs index 660987b..8c75eb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![allow(clippy::collapsible_else_if)] extern crate lazy_static; +extern crate libc_stdhandle; use std::fs::File; use std::io; @@ -49,9 +50,16 @@ fn main() { std::process::exit(0); } + // Create our input *before* constructing the App. When we get the input, + // we use freopen to remap /dev/tty to STDIN so that rustyline works when + // JSON input is provided via STDIN. rustyline gets initialized when we + // create the App, so by putting this before, we make sure rustyline gets + // the /dev/tty input. + let input = Box::new(input::get_input()); let stdout = MouseTerminal::from(HideCursor::from(AlternateScreen::from( io::stdout().into_raw_mode().unwrap(), ))); + let mut app = match App::new(&opt, json_string, input_filename, Box::new(stdout)) { Ok(jl) => jl, Err(err) => { @@ -60,7 +68,7 @@ fn main() { } }; - app.run(Box::new(input::get_input())); + app.run(input); } fn print_pretty_printed_json(json: String) {