mirror of
https://github.com/crossterm-rs/crossterm.git
synced 2025-09-28 13:31:23 +00:00
feat: add try_read
function (#1003)
This adds a new function, `try_read`, that returns `Some(Event)` from the event queue if events are present, and `None` otherwise. This can be used to process multiple events after polling without using a blocking read. ```rust if poll(Duration::from_millis(100))? { // Fetch *all* available events, stopping if this would block while let Some(event) = try_read() { // ... process the event ... } } ``` Closes #972
This commit is contained in:
parent
2ba0160831
commit
6af9116b6a
38
src/event.rs
38
src/event.rs
@ -132,7 +132,11 @@ use derive_more::derive::IsVariant;
|
|||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
pub use stream::EventStream;
|
pub use stream::EventStream;
|
||||||
|
|
||||||
use crate::{csi, event::filter::EventFilter, Command};
|
use crate::{
|
||||||
|
csi,
|
||||||
|
event::{filter::EventFilter, internal::InternalEvent},
|
||||||
|
Command,
|
||||||
|
};
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -225,7 +229,37 @@ pub fn poll(timeout: Duration) -> std::io::Result<bool> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn read() -> std::io::Result<Event> {
|
pub fn read() -> std::io::Result<Event> {
|
||||||
match internal::read(&EventFilter)? {
|
match internal::read(&EventFilter)? {
|
||||||
internal::InternalEvent::Event(event) => Ok(event),
|
InternalEvent::Event(event) => Ok(event),
|
||||||
|
#[cfg(unix)]
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to read a single [`Event`](enum.Event.html) without blocking the thread.
|
||||||
|
///
|
||||||
|
/// If no event is found, `None` is returned.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use crossterm::event::{try_read, poll};
|
||||||
|
/// use std::{io, time::Duration};
|
||||||
|
///
|
||||||
|
/// fn print_all_events() -> io::Result<bool> {
|
||||||
|
/// loop {
|
||||||
|
/// if poll(Duration::from_millis(100))? {
|
||||||
|
/// // Fetch *all* available events at once
|
||||||
|
/// while let Some(event) = try_read() {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn try_read() -> Option<Event> {
|
||||||
|
match internal::try_read(&EventFilter) {
|
||||||
|
Some(InternalEvent::Event(event)) => Some(event),
|
||||||
|
None => None,
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,15 @@ where
|
|||||||
reader.read(filter)
|
reader.read(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a single `InternalEvent`. Non-blocking.
|
||||||
|
pub(crate) fn try_read<F>(filter: &F) -> Option<InternalEvent>
|
||||||
|
where
|
||||||
|
F: Filter,
|
||||||
|
{
|
||||||
|
let mut reader = lock_event_reader();
|
||||||
|
reader.try_read(filter)
|
||||||
|
}
|
||||||
|
|
||||||
/// An internal event.
|
/// An internal event.
|
||||||
///
|
///
|
||||||
/// Encapsulates publicly available `Event` with additional internal
|
/// Encapsulates publicly available `Event` with additional internal
|
||||||
|
@ -96,35 +96,53 @@ impl InternalEventReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blocks the thread until a valid `InternalEvent` can be read.
|
||||||
|
///
|
||||||
|
/// Internally, we use `try_read`, which buffers the events that do not fulfill the filter
|
||||||
|
/// conditions to prevent stalling the thread in an infinite loop.
|
||||||
pub(crate) fn read<F>(&mut self, filter: &F) -> io::Result<InternalEvent>
|
pub(crate) fn read<F>(&mut self, filter: &F) -> io::Result<InternalEvent>
|
||||||
where
|
where
|
||||||
F: Filter,
|
F: Filter,
|
||||||
{
|
{
|
||||||
let mut skipped_events = VecDeque::new();
|
// blocks the thread until a valid event is found
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
while let Some(event) = self.events.pop_front() {
|
if let Some(event) = self.try_read(filter) {
|
||||||
if filter.eval(&event) {
|
return Ok(event);
|
||||||
while let Some(event) = skipped_events.pop_front() {
|
|
||||||
self.events.push_back(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(event);
|
|
||||||
} else {
|
|
||||||
// We can not directly write events back to `self.events`.
|
|
||||||
// If we did, we would put our self's into an endless loop
|
|
||||||
// that would enqueue -> dequeue -> enqueue etc.
|
|
||||||
// This happens because `poll` in this function will always return true if there are events in it's.
|
|
||||||
// And because we just put the non-fulfilling event there this is going to be the case.
|
|
||||||
// Instead we can store them into the temporary buffer,
|
|
||||||
// and then when the filter is fulfilled write all events back in order.
|
|
||||||
skipped_events.push_back(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.poll(None, filter)?;
|
let _ = self.poll(None, filter)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to read the first valid `InternalEvent`.
|
||||||
|
///
|
||||||
|
/// This function checks all events in the queue, and stores events that do not match the
|
||||||
|
/// filter in a buffer to be added back to the queue after all items have been evaluated. We
|
||||||
|
/// must buffer non-fulfilling events because, if added directly back to the queue, they would
|
||||||
|
/// result in an infinite loop, rechecking events that have already been evaluated against the
|
||||||
|
/// filter.
|
||||||
|
pub(crate) fn try_read<F>(&mut self, filter: &F) -> Option<InternalEvent>
|
||||||
|
where
|
||||||
|
F: Filter,
|
||||||
|
{
|
||||||
|
// check all events, storing events that do not match the filter in the `skipped_events`
|
||||||
|
// buffer to be added back later
|
||||||
|
let mut skipped_events = Vec::new();
|
||||||
|
let mut result = None;
|
||||||
|
while let Some(event) = self.events.pop_front() {
|
||||||
|
if filter.eval(&event) {
|
||||||
|
result = Some(event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipped_events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// push all skipped events back to the event queue
|
||||||
|
self.events.extend(skipped_events);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -232,6 +250,28 @@ mod tests {
|
|||||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT);
|
assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn test_try_read_does_not_consume_skipped_event() {
|
||||||
|
const SKIPPED_EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||||
|
const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20);
|
||||||
|
|
||||||
|
let mut reader = InternalEventReader {
|
||||||
|
events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(),
|
||||||
|
source: None,
|
||||||
|
skipped_events: Vec::with_capacity(32),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
reader.try_read(&CursorPositionFilter).unwrap(),
|
||||||
|
CURSOR_EVENT
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
reader.try_read(&InternalEventFilter).unwrap(),
|
||||||
|
SKIPPED_EVENT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_poll_timeouts_if_source_has_no_events() {
|
fn test_poll_timeouts_if_source_has_no_events() {
|
||||||
let source = FakeSource::default();
|
let source = FakeSource::default();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user