Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use select instead of poll on MacOS for /dev/tty #803

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 61 additions & 24 deletions src/tty/unix.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//! Unix specific definitions
#[cfg(feature = "buffer-redux")]
use buffer_redux::BufReader;
use std::cmp;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
Expand All @@ -13,12 +11,15 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, SyncSender};
use std::sync::{Arc, Mutex};

#[cfg(feature = "buffer-redux")]
use buffer_redux::BufReader;
use log::{debug, warn};
use nix::errno::Errno;
use nix::poll::{self, PollFlags, PollTimeout};
use nix::sys::select::{self, FdSet};
#[cfg(not(feature = "termios"))]
use nix::sys::termios::Termios;
use nix::sys::time::TimeValLike;
use nix::unistd::{close, isatty, read, write};
#[cfg(feature = "termios")]
use termios::Termios;
Expand Down Expand Up @@ -195,6 +196,8 @@ pub struct PosixRawReader {
key_map: PosixKeyMap,
// external print reader
pipe_reader: Option<PipeReader>,
#[cfg(target_os = "macos")]
is_dev_tty: bool,
}

impl AsFd for PosixRawReader {
Expand Down Expand Up @@ -243,6 +246,7 @@ impl PosixRawReader {
config: &Config,
key_map: PosixKeyMap,
pipe_reader: Option<PipeReader>,
#[cfg(target_os = "macos")] is_dev_tty: bool,
) -> Self {
let inner = TtyIn { fd, sigwinch_pipe };
#[cfg(any(not(feature = "buffer-redux"), test))]
Expand All @@ -259,6 +263,8 @@ impl PosixRawReader {
parser: Parser::new(),
key_map,
pipe_reader,
#[cfg(target_os = "macos")]
is_dev_tty,
}
}

Expand Down Expand Up @@ -306,9 +312,8 @@ impl PosixRawReader {
match self.poll(timeout) {
// Ignore poll errors, it's very likely we'll pick them up on
// the next read anyway.
Ok(0) | Err(_) => Ok(E::ESC),
Ok(n) => {
debug_assert!(n > 0, "{}", n);
Ok(false) | Err(_) => Ok(E::ESC),
Ok(true) => {
// recurse, and add the alt modifier.
let E(k, m) = self._do_escape_sequence(false)?;
Ok(E(k, m | M::ALT))
Expand Down Expand Up @@ -702,38 +707,53 @@ impl PosixRawReader {
})
}

fn poll(&mut self, timeout_ms: PollTimeout) -> Result<i32> {
fn poll(&mut self, timeout: PollTimeout) -> Result<bool> {
let n = self.tty_in.buffer().len();
if n > 0 {
return Ok(n as i32);
return Ok(true);
}
#[cfg(target_os = "macos")]
if self.is_dev_tty {
// poll doesn't work for /dev/tty on MacOS but select does
return Ok(match self.select(Some(timeout), false /* ignored */)? {
// ugly but ESC means timeout...
Event::KeyPress(KeyEvent::ESC) => false,
_ => true,
});
}
debug!(target: "rustyline", "poll with: {:?}", timeout);
let mut fds = [poll::PollFd::new(self.as_fd(), PollFlags::POLLIN)];
let r = poll::poll(&mut fds, timeout_ms);
let r = poll::poll(&mut fds, timeout);
debug!(target: "rustyline", "poll returns: {:?}", r);
match r {
Ok(n) => Ok(n),
Ok(n) => Ok(n != 0),
Err(Errno::EINTR) => {
if self.tty_in.get_ref().sigwinch()? {
Err(ReadlineError::WindowResized)
} else {
Ok(0) // Ignore EINTR while polling
Ok(false) // Ignore EINTR while polling
}
}
Err(e) => Err(e.into()),
}
}

fn select(&mut self, single_esc_abort: bool) -> Result<Event> {
// timeout is used only with /dev/tty on MacOs
fn select(&mut self, timeout: Option<PollTimeout>, single_esc_abort: bool) -> Result<Event> {
let tty_in = self.as_fd();
let sigwinch_pipe = self
.tty_in
.get_ref()
.sigwinch_pipe
.map(|fd| unsafe { BorrowedFd::borrow_raw(fd) });
let pipe_reader = self
.pipe_reader
.as_ref()
.map(|pr| pr.lock().unwrap().0.as_raw_fd())
.map(|fd| unsafe { BorrowedFd::borrow_raw(fd) });
let pipe_reader = if timeout.is_some() {
None
} else {
self.pipe_reader
.as_ref()
.map(|pr| pr.lock().unwrap().0.as_raw_fd())
.map(|fd| unsafe { BorrowedFd::borrow_raw(fd) })
};
loop {
let mut readfds = FdSet::new();
if let Some(sigwinch_pipe) = sigwinch_pipe {
Expand All @@ -743,7 +763,14 @@ impl PosixRawReader {
if let Some(pipe_reader) = pipe_reader {
readfds.insert(pipe_reader);
}
if let Err(err) = select::select(None, Some(&mut readfds), None, None, None) {
let mut timeout = match timeout {
Some(pt) => pt
.as_millis()
.map(|ms| nix::sys::time::TimeVal::milliseconds(ms as i64)),
None => None,
};
if let Err(err) = select::select(None, Some(&mut readfds), None, None, timeout.as_mut())
{
if err == Errno::EINTR && self.tty_in.get_ref().sigwinch()? {
return Err(ReadlineError::WindowResized);
} else if err != Errno::EINTR {
Expand All @@ -756,8 +783,16 @@ impl PosixRawReader {
self.tty_in.get_ref().sigwinch()?;
return Err(ReadlineError::WindowResized);
} else if readfds.contains(tty_in) {
// prefer user input over external print
return self.next_key(single_esc_abort).map(Event::KeyPress);
if timeout.is_some() {
// ugly but ENTER means success (no timeout)...
return Ok(Event::KeyPress(KeyEvent::ENTER));
} else {
// prefer user input over external print
return self.next_key(single_esc_abort).map(Event::KeyPress);
}
} else if timeout.is_some() {
// ugly but ESC means timeout...
return Ok(Event::KeyPress(KeyEvent::ESC));
} else if let Some(ref pipe_reader) = self.pipe_reader {
let mut guard = pipe_reader.lock().unwrap();
let mut buf = [0; 1];
Expand All @@ -776,14 +811,14 @@ impl RawReader for PosixRawReader {
#[cfg(not(feature = "signal-hook"))]
fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
match self.pipe_reader {
Some(_) => self.select(single_esc_abort),
Some(_) => self.select(None, single_esc_abort),
None => self.next_key(single_esc_abort).map(Event::KeyPress),
}
}

#[cfg(feature = "signal-hook")]
fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
self.select(single_esc_abort)
self.select(None, single_esc_abort)
}

fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent> {
Expand All @@ -800,7 +835,7 @@ impl RawReader for PosixRawReader {
self.timeout_ms
};
match self.poll(timeout_ms) {
Ok(0) => {
Ok(false) => {
// single escape
}
Ok(_) => {
Expand Down Expand Up @@ -1117,14 +1152,14 @@ impl Renderer for PosixRenderer {
}

fn move_cursor_at_leftmost(&mut self, rdr: &mut PosixRawReader) -> Result<()> {
if rdr.poll(PollTimeout::ZERO)? != 0 {
if rdr.poll(PollTimeout::ZERO)? {
debug!(target: "rustyline", "cannot request cursor location");
return Ok(());
}
/* Report cursor location */
self.write_and_flush("\x1b[6n")?;
/* Read the response: ESC [ rows ; cols R */
if rdr.poll(PollTimeout::from(100u8))? == 0
if !rdr.poll(PollTimeout::from(100u8))?
|| rdr.next_char()? != '\x1b'
|| rdr.next_char()? != '['
|| read_digits_until(rdr, ';')?.is_none()
Expand Down Expand Up @@ -1420,6 +1455,8 @@ impl Term for PosixTerminal {
config,
key_map,
self.pipe_reader.clone(),
#[cfg(target_os = "macos")]
self.close_on_drop,
)
}

Expand Down