use super::parser::{Parser, Pending};
use super::Event;
use crate::base::{PixelSize, Point, Result};
use crate::term::caps::Capabilities;
use crate::term::probe::ActiveProbe;
use crate::term::{TermRead, Terminal};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
pub const DEFAULT_ESC_TIMEOUT: Duration = Duration::from_millis(30);
pub const DEFAULT_SEQ_TIMEOUT: Duration = Duration::from_millis(500);
pub struct EventReader {
parser: Parser,
queue: VecDeque<Event>,
scratch: Vec<Event>,
pending_since: Option<Instant>,
pixel_cell: Option<PixelSize>,
pub esc_timeout: Duration,
pub seq_timeout: Duration,
}
impl Default for EventReader {
fn default() -> Self {
Self::new()
}
}
impl EventReader {
pub fn new() -> Self {
EventReader {
parser: Parser::new(),
queue: VecDeque::new(),
scratch: Vec::new(),
pending_since: None,
pixel_cell: None,
esc_timeout: DEFAULT_ESC_TIMEOUT,
seq_timeout: DEFAULT_SEQ_TIMEOUT,
}
}
pub fn parser(&mut self) -> &mut Parser {
&mut self.parser
}
pub fn enable_pixel_mouse(&mut self, cell: PixelSize) {
if !cell.is_empty() {
self.pixel_cell = Some(cell);
}
}
pub fn disable_pixel_mouse(&mut self) {
self.pixel_cell = None;
}
fn pending_deadline(&self) -> Option<Instant> {
let since = self.pending_since?;
match self.parser.pending() {
Pending::None => None,
Pending::BareEsc => Some(since + self.esc_timeout),
Pending::Sequence => Some(since + self.seq_timeout),
}
}
fn note_pending(&mut self) {
self.pending_since = match self.parser.pending() {
Pending::None => None,
_ => Some(Instant::now()),
};
}
fn drain_scratch(&mut self) {
if let Some(cell) = self.pixel_cell {
for ev in &mut self.scratch {
convert_pixels(cell, ev);
}
}
self.queue.extend(self.scratch.drain(..));
}
pub fn poll_event(
&mut self,
term: &mut dyn Terminal,
deadline: Option<Instant>,
) -> Result<Option<Event>> {
loop {
if let Some(ev) = self.queue.pop_front() {
return Ok(Some(ev));
}
let pending = self.pending_deadline();
let effective = match (deadline, pending) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
};
match term.read(effective)? {
TermRead::Input(bytes) => {
self.parser.feed(bytes, &mut self.scratch);
self.drain_scratch();
self.note_pending();
}
TermRead::Resize(sz) => return Ok(Some(Event::Resize(sz))),
TermRead::Wake => return Ok(None),
TermRead::Idle => {
let now = Instant::now();
if let Some(p) = self.pending_deadline() {
if now >= p {
self.parser.flush_pending(&mut self.scratch);
self.drain_scratch();
self.pending_since = None;
continue; }
}
if let Some(d) = deadline {
if now >= d {
return Ok(None);
}
}
}
}
}
}
}
impl EventReader {
pub fn poll_many(
&mut self,
term: &mut dyn Terminal,
out: &mut Vec<Event>,
deadline: Option<Instant>,
) -> Result<usize> {
let mut appended = 0usize;
match self.poll_event(term, deadline)? {
Some(ev) => {
out.push(ev);
appended += 1;
}
None => return Ok(0),
}
loop {
let now = Instant::now();
match self.poll_event(term, Some(now))? {
Some(ev) => {
out.push(ev);
appended += 1;
}
None => return Ok(appended),
}
}
}
}
fn convert_pixels(cell: PixelSize, ev: &mut Event) {
if let Event::Mouse(m) = ev {
let raw = m.pos;
m.pixel = Some(raw);
m.pos = Point::new(
(raw.x / i32::from(cell.w)).max(0),
(raw.y / i32::from(cell.h)).max(0),
);
}
}
pub fn probe_active(
term: &mut dyn Terminal,
reader: &mut EventReader,
caps: &mut Capabilities,
timeout: Duration,
) -> Result<Vec<Event>> {
if caps.dumb {
return Ok(Vec::new());
}
let _ = crate::term::probe::refresh_cell_pixel_size(term, caps);
let mut probe = ActiveProbe::for_caps(caps);
term.write(&probe.full_query_bytes())?;
term.flush()?;
let deadline = Instant::now() + timeout;
let mut grace: Option<Instant> = None;
let mut passthrough = Vec::new();
loop {
let effective = grace.map_or(deadline, |g| g.min(deadline));
match reader.poll_event(term, Some(effective))? {
Some(Event::CapsReply(reply)) => {
if probe.on_reply(&reply, caps) {
break; }
if probe.sentinel_passed() && probe.awaiting_wrapped() && grace.is_none() {
grace = Some(Instant::now() + crate::term::probe::TMUX_GRACE);
}
}
Some(other) => passthrough.push(other),
None => {
let now = Instant::now();
if now >= deadline {
break; }
if grace.is_some_and(|g| now >= g) {
break; }
}
}
}
Ok(passthrough)
}
#[cfg(test)]
#[path = "reader_tests.rs"]
mod tests;