terminal_io/
terminal_duplexer.rs

1//! The `TerminalDuplex` struct.
2
3use crate::config::{detect_read_write_config, ReadConfig, WriteConfig};
4use crate::{DuplexTerminal, ReadTerminal, Terminal, TerminalColorSupport, WriteTerminal};
5use duplex::{Duplex, HalfDuplex};
6use io_extras::grip::AsReadWriteGrip;
7#[cfg(windows)]
8use io_extras::os::windows::{
9    AsRawReadWriteHandleOrSocket, AsReadWriteHandleOrSocket, BorrowedHandleOrSocket,
10    RawHandleOrSocket,
11};
12use std::fmt;
13use std::io::{self, IoSlice, IoSliceMut, Read, Write};
14#[cfg(not(windows))]
15use {
16    io_extras::os::rustix::{AsRawReadWriteFd, AsReadWriteFd, RawFd},
17    std::os::fd::BorrowedFd,
18};
19
20/// A wrapper around a `Read` + `Write` which adds minimal terminal support.
21#[derive(Debug)]
22pub struct TerminalDuplexer<Inner: Duplex> {
23    inner: Inner,
24    read_config: Option<ReadConfig>,
25    write_config: Option<WriteConfig>,
26}
27
28impl<Inner: Duplex + AsReadWriteGrip> TerminalDuplexer<Inner> {
29    /// Wrap a `TerminalDuplex` around the given stream, autodetecting
30    /// terminal properties using its `AsGrip` implementation.
31    pub fn with_handle(inner: Inner) -> Self {
32        let (read_config, write_config) = detect_read_write_config(&inner);
33        Self {
34            inner,
35            read_config,
36            write_config,
37        }
38    }
39}
40
41impl<Inner: Duplex + Read + Write> TerminalDuplexer<Inner> {
42    /// Wrap a `TerminalReader` around the given stream, using
43    /// conservative terminal properties.
44    pub fn generic(inner: Inner) -> Self {
45        Self {
46            inner,
47            read_config: None,
48            write_config: None,
49        }
50    }
51
52    /// Consume `self` and return the inner stream.
53    #[inline]
54    pub fn into_inner(self) -> Inner {
55        self.inner
56    }
57
58    fn reset(&mut self) {
59        if self.is_output_terminal() {
60            self.write_all("\x1b[!p\r\x1b[K".as_bytes()).ok();
61        }
62    }
63}
64
65#[cfg(not(windows))]
66impl<Inner: Duplex + AsRawReadWriteFd> AsRawReadWriteFd for TerminalDuplexer<Inner> {
67    #[inline]
68    fn as_raw_read_fd(&self) -> RawFd {
69        self.inner.as_raw_read_fd()
70    }
71
72    #[inline]
73    fn as_raw_write_fd(&self) -> RawFd {
74        self.inner.as_raw_write_fd()
75    }
76}
77
78#[cfg(not(windows))]
79impl<Inner: Duplex + AsReadWriteFd> AsReadWriteFd for TerminalDuplexer<Inner> {
80    #[inline]
81    fn as_read_fd(&self) -> BorrowedFd<'_> {
82        self.inner.as_read_fd()
83    }
84
85    #[inline]
86    fn as_write_fd(&self) -> BorrowedFd<'_> {
87        self.inner.as_write_fd()
88    }
89}
90
91#[cfg(windows)]
92impl<Inner: Duplex + AsRawReadWriteHandleOrSocket> AsRawReadWriteHandleOrSocket
93    for TerminalDuplexer<Inner>
94{
95    #[inline]
96    fn as_raw_read_handle_or_socket(&self) -> RawHandleOrSocket {
97        self.inner.as_raw_read_handle_or_socket()
98    }
99
100    #[inline]
101    fn as_raw_write_handle_or_socket(&self) -> RawHandleOrSocket {
102        self.inner.as_raw_write_handle_or_socket()
103    }
104}
105
106#[cfg(windows)]
107impl<Inner: Duplex + AsReadWriteHandleOrSocket> AsReadWriteHandleOrSocket
108    for TerminalDuplexer<Inner>
109{
110    #[inline]
111    fn as_read_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> {
112        self.inner.as_read_handle_or_socket()
113    }
114
115    #[inline]
116    fn as_write_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> {
117        self.inner.as_write_handle_or_socket()
118    }
119}
120
121impl<Inner: Duplex> Terminal for TerminalDuplexer<Inner> {}
122
123impl<Inner: Duplex + Read + Write> ReadTerminal for TerminalDuplexer<Inner> {
124    fn is_line_by_line(&self) -> bool {
125        self.read_config.as_ref().map_or(false, |c| c.line_by_line)
126    }
127
128    fn is_input_terminal(&self) -> bool {
129        self.read_config.is_some()
130    }
131}
132
133impl<Inner: Duplex + Read + Write> WriteTerminal for TerminalDuplexer<Inner> {
134    fn color_support(&self) -> TerminalColorSupport {
135        self.write_config
136            .as_ref()
137            .map_or_else(Default::default, |c| c.color_support)
138    }
139
140    fn color_preference(&self) -> bool {
141        self.write_config
142            .as_ref()
143            .map_or(false, |c| c.color_preference)
144    }
145
146    fn is_output_terminal(&self) -> bool {
147        self.write_config.is_some()
148    }
149}
150
151impl<Inner: Duplex + HalfDuplex> DuplexTerminal for TerminalDuplexer<Inner> {}
152
153impl<Inner: Duplex + Read + Write> Read for TerminalDuplexer<Inner> {
154    #[inline]
155    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
156        match self.inner.read(buf) {
157            Ok(0) if !buf.is_empty() => {
158                self.reset();
159                Ok(0)
160            }
161            Ok(n) => Ok(n),
162            Err(e) => Err(e),
163        }
164    }
165
166    #[inline]
167    fn read_vectored(&mut self, bufs: &mut [IoSliceMut]) -> io::Result<usize> {
168        match self.inner.read_vectored(bufs) {
169            Ok(0) if bufs.iter().any(|b| !b.is_empty()) => {
170                self.reset();
171                Ok(0)
172            }
173            Ok(n) => Ok(n),
174            Err(e) => Err(e),
175        }
176    }
177
178    #[cfg(can_vector)]
179    #[inline]
180    fn is_read_vectored(&self) -> bool {
181        self.inner.is_read_vectored()
182    }
183
184    #[inline]
185    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
186        let n = self.inner.read_to_end(buf)?;
187        self.reset();
188        Ok(n)
189    }
190
191    #[inline]
192    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
193        let n = self.inner.read_to_string(buf)?;
194        self.reset();
195        Ok(n)
196    }
197
198    #[inline]
199    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
200        match self.inner.read_exact(buf) {
201            Ok(()) => Ok(()),
202            Err(e) => {
203                if e.kind() == io::ErrorKind::UnexpectedEof {
204                    self.reset();
205                }
206                Err(e)
207            }
208        }
209    }
210}
211
212impl<Inner: Duplex + Read + Write> Write for TerminalDuplexer<Inner> {
213    #[inline]
214    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
215        self.inner.write(buf)
216    }
217
218    #[inline]
219    fn flush(&mut self) -> io::Result<()> {
220        self.inner.flush()
221    }
222
223    #[inline]
224    fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
225        self.inner.write_vectored(bufs)
226    }
227
228    #[cfg(can_vector)]
229    #[inline]
230    fn is_write_vectored(&self) -> bool {
231        self.inner.is_write_vectored()
232    }
233
234    #[inline]
235    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
236        self.inner.write_all(buf)
237    }
238
239    #[cfg(write_all_vectored)]
240    #[inline]
241    fn write_all_vectored(&mut self, bufs: &mut [IoSlice]) -> io::Result<()> {
242        self.inner.write_all_vectored(bufs)
243    }
244
245    #[inline]
246    fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
247        self.inner.write_fmt(fmt)
248    }
249}
250
251impl<Inner: Duplex> Duplex for TerminalDuplexer<Inner> {}