Skip to main content

termion/
async.rs

1use std::io::{self, Read};
2use std::sync::mpsc;
3use std::thread;
4
5use sys::tty::get_tty;
6
7/// Construct an asynchronous handle to the TTY standard input, with a delimiter byte.
8///
9/// This has the same advantages as async_stdin(), but also allows specifying a delimiter byte. The
10/// reader will stop reading after consuming the delimiter byte.
11pub fn async_stdin_until(delimiter: u8) -> AsyncReader {
12    let (send, recv) = mpsc::channel();
13
14    thread::spawn(move || {
15        for i in get_tty().unwrap().bytes() {
16            match i {
17                Ok(byte) => {
18                    let end_of_stream = &byte == &delimiter;
19                    let send_error = send.send(Ok(byte)).is_err();
20
21                    if end_of_stream || send_error {
22                        return;
23                    }
24                }
25                Err(_) => {
26                    return;
27                }
28            }
29        }
30    });
31
32    AsyncReader { recv: recv }
33}
34
35/// Construct an asynchronous handle to the TTY standard input.
36///
37/// This allows you to read from standard input _without blocking_ the current thread.
38/// Specifically, it works by firing up another thread to handle the event stream, which will then
39/// be buffered in a mpsc queue, which will eventually be read by the current thread.
40///
41/// This will not read the piped standard input, but rather read from the TTY device, since reading
42/// asyncronized from piped input would rarely make sense. In other words, if you pipe standard
43/// output from another process, it won't be reflected in the stream returned by this function, as
44/// this represents the TTY device, and not the piped standard input.
45pub fn async_stdin() -> AsyncReader {
46    let (send, recv) = mpsc::channel();
47
48    thread::spawn(move || {
49        for i in get_tty().unwrap().bytes() {
50            if send.send(i).is_err() {
51                return;
52            }
53        }
54    });
55
56    AsyncReader { recv: recv }
57}
58
59/// An asynchronous reader.
60///
61/// This acts as any other stream, with the exception that reading from it won't block. Instead,
62/// the buffer will only be partially updated based on how much the internal buffer holds.
63pub struct AsyncReader {
64    /// The underlying mpsc receiver.
65    recv: mpsc::Receiver<io::Result<u8>>,
66}
67
68// FIXME: Allow constructing an async reader from an arbitrary stream.
69
70impl Read for AsyncReader {
71    /// Read from the byte stream.
72    ///
73    /// This will never block, but try to drain the event queue until empty. If the total number of
74    /// bytes written is lower than the buffer's length, the event queue is empty or that the event
75    /// stream halted.
76    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
77        let mut total = 0;
78
79        loop {
80            if total >= buf.len() {
81                break;
82            }
83
84            match self.recv.try_recv() {
85                Ok(Ok(b)) => {
86                    buf[total] = b;
87                    total += 1;
88                }
89                Ok(Err(e)) => return Err(e),
90                Err(_) => break,
91            }
92        }
93
94        Ok(total)
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use super::*;
101    use std::io::Read;
102
103    #[test]
104    fn test_async_stdin() {
105        let stdin = async_stdin();
106        stdin.bytes().next();
107    }
108}