mirror of
https://github.com/crossterm-rs/crossterm.git
synced 2025-09-27 13:01: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")]
|
||||
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::time::Duration;
|
||||
|
||||
@ -225,7 +229,37 @@ pub fn poll(timeout: Duration) -> std::io::Result<bool> {
|
||||
/// ```
|
||||
pub fn read() -> std::io::Result<Event> {
|
||||
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)]
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -52,6 +52,15 @@ where
|
||||
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.
|
||||
///
|
||||
/// 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>
|
||||
where
|
||||
F: Filter,
|
||||
{
|
||||
let mut skipped_events = VecDeque::new();
|
||||
|
||||
// blocks the thread until a valid event is found
|
||||
loop {
|
||||
while let Some(event) = self.events.pop_front() {
|
||||
if filter.eval(&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);
|
||||
}
|
||||
if let Some(event) = self.try_read(filter) {
|
||||
return Ok(event);
|
||||
}
|
||||
|
||||
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)]
|
||||
@ -232,6 +250,28 @@ mod tests {
|
||||
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]
|
||||
fn test_poll_timeouts_if_source_has_no_events() {
|
||||
let source = FakeSource::default();
|
||||
|
Loading…
x
Reference in New Issue
Block a user