terminal_io/
terminal_writer.rs

1//! The `TerminalWriter` struct.
2
3use crate::config::{detect_write_config, WriteConfig};
4use crate::{Terminal, TerminalColorSupport, WriteTerminal};
5use io_extras::grip::AsGrip;
6#[cfg(windows)]
7use io_extras::os::windows::{
8    AsHandleOrSocket, AsRawHandleOrSocket, BorrowedHandleOrSocket, RawHandleOrSocket,
9};
10use std::fmt;
11use std::io::{self, IoSlice, Write};
12#[cfg(not(windows))]
13use {
14    io_extras::os::rustix::{AsRawFd, RawFd},
15    std::os::fd::{AsFd, BorrowedFd},
16};
17
18/// A wrapper around a `Write` which adds minimal terminal support.
19#[derive(Debug)]
20pub struct TerminalWriter<Inner: Write> {
21    inner: Inner,
22    write_config: Option<WriteConfig>,
23}
24
25impl<Inner: Write + AsGrip> TerminalWriter<Inner> {
26    /// Wrap a `TerminalWriter` around the given stream, autodetecting
27    /// terminal properties using its `AsGrip` implementation.
28    pub fn with_handle(inner: Inner) -> Self {
29        let write_config = detect_write_config(&inner);
30        Self {
31            inner,
32            write_config,
33        }
34    }
35
36    /// Wrap a `TerminalWriter` around the given stream, using the given
37    /// terminal properties.
38    pub fn from(
39        inner: Inner,
40        is_terminal: bool,
41        color_support: TerminalColorSupport,
42        color_preference: bool,
43    ) -> Self {
44        Self {
45            inner,
46            write_config: if is_terminal {
47                Some(WriteConfig {
48                    color_support,
49                    color_preference,
50                })
51            } else {
52                None
53            },
54        }
55    }
56}
57
58impl<Inner: Write> TerminalWriter<Inner> {
59    /// Wrap a `TerminalWriter` around the given stream, using
60    /// conservative terminal properties.
61    pub fn generic(inner: Inner) -> Self {
62        Self {
63            inner,
64            write_config: None,
65        }
66    }
67
68    /// Consume `self` and return the inner stream.
69    #[inline]
70    pub fn into_inner(self) -> Inner {
71        self.inner
72    }
73}
74
75#[cfg(not(windows))]
76impl<Inner: Write + AsRawFd> AsRawFd for TerminalWriter<Inner> {
77    #[inline]
78    fn as_raw_fd(&self) -> RawFd {
79        self.inner.as_raw_fd()
80    }
81}
82
83#[cfg(not(windows))]
84impl<Inner: Write + AsFd> AsFd for TerminalWriter<Inner> {
85    #[inline]
86    fn as_fd(&self) -> BorrowedFd {
87        self.inner.as_fd()
88    }
89}
90
91#[cfg(windows)]
92impl<Inner: Write + AsRawHandleOrSocket> AsRawHandleOrSocket for TerminalWriter<Inner> {
93    #[inline]
94    fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket {
95        self.inner.as_raw_handle_or_socket()
96    }
97}
98
99#[cfg(windows)]
100impl<Inner: Write + AsHandleOrSocket> AsHandleOrSocket for TerminalWriter<Inner> {
101    #[inline]
102    fn as_handle_or_socket(&self) -> BorrowedHandleOrSocket<'_> {
103        self.inner.as_handle_or_socket()
104    }
105}
106
107impl<Inner: Write> Terminal for TerminalWriter<Inner> {}
108
109impl<Inner: Write> WriteTerminal for TerminalWriter<Inner> {
110    fn color_support(&self) -> TerminalColorSupport {
111        self.write_config
112            .as_ref()
113            .map_or_else(Default::default, |c| c.color_support)
114    }
115
116    fn color_preference(&self) -> bool {
117        self.write_config
118            .as_ref()
119            .map_or(false, |c| c.color_preference)
120    }
121
122    fn is_output_terminal(&self) -> bool {
123        self.write_config.is_some()
124    }
125}
126
127impl<Inner: Write> Write for TerminalWriter<Inner> {
128    #[inline]
129    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
130        self.inner.write(buf)
131    }
132
133    #[inline]
134    fn flush(&mut self) -> io::Result<()> {
135        self.inner.flush()
136    }
137
138    #[inline]
139    fn write_vectored(&mut self, bufs: &[IoSlice]) -> io::Result<usize> {
140        self.inner.write_vectored(bufs)
141    }
142
143    #[cfg(can_vector)]
144    #[inline]
145    fn is_write_vectored(&self) -> bool {
146        self.inner.is_write_vectored()
147    }
148
149    #[inline]
150    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
151        self.inner.write_all(buf)
152    }
153
154    #[cfg(write_all_vectored)]
155    #[inline]
156    fn write_all_vectored(&mut self, bufs: &mut [IoSlice]) -> io::Result<()> {
157        self.inner.write_all_vectored(bufs)
158    }
159
160    #[inline]
161    fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
162        self.inner.write_fmt(fmt)
163    }
164}