Skip to content

Commit

Permalink
Use freopen to move /dev/tty input to stdin so rustyline works when p…
Browse files Browse the repository at this point in the history
…iping in input. (#32)
  • Loading branch information
PaulJuliusMartinez authored Feb 20, 2022
1 parent 46c7be3 commit 4a2928b
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 9 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
31 changes: 23 additions & 8 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,13 +14,29 @@ const BUFFER_SIZE: usize = 1024;
const ESCAPE: u8 = 0o33;

pub fn get_input() -> impl Iterator<Item = io::Result<TuiEvent>> {
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<usize> {
fn read_and_retry_on_interrupt(input: &mut Stdin, buf: &mut [u8]) -> io::Result<usize> {
loop {
match input.read(buf) {
res @ Ok(_) => {
Expand All @@ -38,15 +53,15 @@ fn read_and_retry_on_interrupt(input: &mut File, buf: &mut [u8]) -> io::Result<u
}

struct BufferedInput<const N: usize> {
input: File,
input: Stdin,
buffer: [u8; N],
buffer_size: usize,
buffer_index: usize,
might_have_more_data: bool,
}

impl<const N: usize> BufferedInput<N> {
fn new(input: File) -> BufferedInput<N> {
fn new(input: Stdin) -> BufferedInput<N> {
BufferedInput {
input,
buffer: [0; N],
Expand Down Expand Up @@ -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();

Expand Down
10 changes: 9 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![allow(clippy::collapsible_else_if)]

extern crate lazy_static;
extern crate libc_stdhandle;

use std::fs::File;
use std::io;
Expand Down Expand Up @@ -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) => {
Expand All @@ -60,7 +68,7 @@ fn main() {
}
};

app.run(Box::new(input::get_input()));
app.run(input);
}

fn print_pretty_printed_json(json: String) {
Expand Down

0 comments on commit 4a2928b

Please sign in to comment.