Skip to content

Commit

Permalink
Use select instead of poll on MacOS for /dev/tty
Browse files Browse the repository at this point in the history
  • Loading branch information
gwenn committed Sep 8, 2024
1 parent fcbca98 commit 464efa6
Showing 1 changed file with 61 additions and 23 deletions.
84 changes: 61 additions & 23 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,16 @@ 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;
#[cfg(target_os = "macos")]
use nix::sys::time::TimeValLike;
use nix::unistd::{close, isatty, read, write};
#[cfg(feature = "termios")]
use termios::Termios;
Expand Down Expand Up @@ -195,6 +197,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 +247,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 +264,8 @@ impl PosixRawReader {
parser: Parser::new(),
key_map,
pipe_reader,
#[cfg(target_os = "macos")]
is_dev_tty,
}
}

Expand Down Expand Up @@ -306,9 +313,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 +708,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 +764,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 +784,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,7 +812,7 @@ 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),
}
}
Expand All @@ -800,7 +836,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 +1153,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 +1456,8 @@ impl Term for PosixTerminal {
config,
key_map,
self.pipe_reader.clone(),
#[cfg(target_os = "macos")]
self.close_on_drop,
)
}

Expand Down

0 comments on commit 464efa6

Please sign in to comment.