Skip to content

Commit

Permalink
Merge pull request #803 from gwenn/force_select_on_macos
Browse files Browse the repository at this point in the history
Use select instead of poll on MacOS for /dev/tty
  • Loading branch information
gwenn authored Dec 14, 2024
2 parents dbcb7c0 + 2085d0c commit 964fbad
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ impl<'b> InputState<'b> {
tty::Event::ExternalPrint(msg) => {
wrt.external_print(msg)?;
}
#[cfg(target_os = "macos")]
_ => {}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/tty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub trait RawMode: Sized {
pub enum Event {
KeyPress(KeyEvent),
ExternalPrint(String),
#[cfg(target_os = "macos")]
Timeout(bool),
}

/// Translate bytes read from stdin to keys.
Expand Down
81 changes: 59 additions & 22 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 @@ -190,6 +191,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 @@ -238,6 +241,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 @@ -254,6 +258,8 @@ impl PosixRawReader {
parser: Parser::new(),
key_map,
pipe_reader,
#[cfg(target_os = "macos")]
is_dev_tty,
}
}

Expand Down Expand Up @@ -301,9 +307,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 @@ -697,38 +702,52 @@ 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 */)? {
Event::Timeout(true) => 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 @@ -738,7 +757,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 @@ -751,8 +777,17 @@ impl PosixRawReader {
self.tty_in.get_ref().sigwinch()?;
return Err(ReadlineError::WindowResized);
} else if readfds.contains(tty_in) {
#[cfg(target_os = "macos")]
if timeout.is_some() {
return Ok(Event::Timeout(false));
}
// prefer user input over external print
return self.next_key(single_esc_abort).map(Event::KeyPress);
} else if timeout.is_some() {
#[cfg(target_os = "macos")]
return Ok(Event::Timeout(true));
#[cfg(not(target_os = "macos"))]
unreachable!()
} else if let Some(ref pipe_reader) = self.pipe_reader {
let mut guard = pipe_reader.lock().unwrap();
let mut buf = [0; 1];
Expand All @@ -771,14 +806,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 @@ -795,7 +830,7 @@ impl RawReader for PosixRawReader {
self.timeout_ms
};
match self.poll(timeout_ms) {
Ok(0) => {
Ok(false) => {
// single escape
}
Ok(_) => {
Expand Down Expand Up @@ -1112,14 +1147,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 @@ -1414,6 +1449,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 964fbad

Please sign in to comment.