terminal_io/
terminal_duplexer.rs1use 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#[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 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 pub fn generic(inner: Inner) -> Self {
45 Self {
46 inner,
47 read_config: None,
48 write_config: None,
49 }
50 }
51
52 #[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> {}