1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use super::events::{AppEvent, Event, MouseEvent, MouseEventKind};
use futures_lite::{io, stream::Fuse, Stream, StreamExt};
use std::{
    pin::Pin,
    task::{Context, Poll},
};

/// Terminal input stream decodes ANSI sequences and content and includes window resize events
///
/// It will not do much of resizing without a control FD/Handle that can report it.
pub(crate) fn squash_input<S, A: AppEvent>(input: S) -> Squash<S>
where
    S: Stream<Item = io::Result<Event<A>>>,
{
    Squash::new(input)
}

pub(crate) struct Squash<S> {
    inner: Pin<Box<Fuse<S>>>,
}

impl<S, A: AppEvent> Squash<S>
where
    S: Stream<Item = io::Result<Event<A>>>,
{
    pub fn new(inner: S) -> Self {
        Self {
            inner: Box::pin(inner.fuse()),
        }
    }
}

impl<S, A: AppEvent> Stream for Squash<S>
where
    S: Stream<Item = io::Result<Event<A>>>,
{
    type Item = Vec<io::Result<Event<A>>>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let mut pending = vec![];
        let mut beats = 0;
        let readysome = |mut pending: Vec<io::Result<Event<A>>>, beats, otherwise| {
            if beats != 0 || !pending.is_empty() {
                pending.push(Ok(Event::Refresh(beats)));
                Poll::Ready(Some(pending))
            } else {
                otherwise
            }
        };
        loop {
            break match self.inner.as_mut().poll_next(cx) {
                Poll::Pending => readysome(pending, beats, Poll::Pending),
                Poll::Ready(None) => readysome(pending, beats, Poll::Ready(None)),
                Poll::Ready(Some(next)) => {
                    match (pending.last(), &next) {
                        // squash refresh events to the end adding the beats
                        (_, Ok(Event::Refresh(n2))) => {
                            beats += n2;
                            continue;
                        }
                        // squash mouse move events, keeping the last in a row
                        (
                            Some(Ok(Event::Mouse(MouseEvent {
                                kind: MouseEventKind::Moved,
                                ..
                            }))),
                            Ok(Event::Mouse(MouseEvent {
                                kind: MouseEventKind::Moved,
                                ..
                            })),
                        ) => {
                            pending.pop();
                        }
                        _ => {}
                    }
                    pending.push(next);
                    continue;
                }
            };
        }
    }
}