1 /// Terminal-styling container
2 ///
3 /// For now, this is the same as a [`Str`][crate::builder::Str]. This exists to reserve space in
4 /// the API for exposing terminal styling.
5 #[derive(Clone, Default, Debug, PartialEq, Eq)]
6 pub struct StyledStr {
7 #[cfg(feature = "color")]
8 pieces: Vec<(Option<Style>, String)>,
9 #[cfg(not(feature = "color"))]
10 pieces: String,
11 }
12
13 impl StyledStr {
14 /// Create an empty buffer
15 #[cfg(feature = "color")]
new() -> Self16 pub const fn new() -> Self {
17 Self { pieces: Vec::new() }
18 }
19
20 /// Create an empty buffer
21 #[cfg(not(feature = "color"))]
new() -> Self22 pub const fn new() -> Self {
23 Self {
24 pieces: String::new(),
25 }
26 }
27
28 /// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
29 #[cfg(feature = "color")]
ansi(&self) -> impl std::fmt::Display + '_30 pub fn ansi(&self) -> impl std::fmt::Display + '_ {
31 AnsiDisplay { styled: self }
32 }
33
header(&mut self, msg: impl Into<String>)34 pub(crate) fn header(&mut self, msg: impl Into<String>) {
35 self.stylize_(Some(Style::Header), msg.into());
36 }
37
literal(&mut self, msg: impl Into<String>)38 pub(crate) fn literal(&mut self, msg: impl Into<String>) {
39 self.stylize_(Some(Style::Literal), msg.into());
40 }
41
placeholder(&mut self, msg: impl Into<String>)42 pub(crate) fn placeholder(&mut self, msg: impl Into<String>) {
43 self.stylize_(Some(Style::Placeholder), msg.into());
44 }
45
46 #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
good(&mut self, msg: impl Into<String>)47 pub(crate) fn good(&mut self, msg: impl Into<String>) {
48 self.stylize_(Some(Style::Good), msg.into());
49 }
50
51 #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
warning(&mut self, msg: impl Into<String>)52 pub(crate) fn warning(&mut self, msg: impl Into<String>) {
53 self.stylize_(Some(Style::Warning), msg.into());
54 }
55
error(&mut self, msg: impl Into<String>)56 pub(crate) fn error(&mut self, msg: impl Into<String>) {
57 self.stylize_(Some(Style::Error), msg.into());
58 }
59
60 #[allow(dead_code)]
hint(&mut self, msg: impl Into<String>)61 pub(crate) fn hint(&mut self, msg: impl Into<String>) {
62 self.stylize_(Some(Style::Hint), msg.into());
63 }
64
none(&mut self, msg: impl Into<String>)65 pub(crate) fn none(&mut self, msg: impl Into<String>) {
66 self.stylize_(None, msg.into());
67 }
68
stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>)69 pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
70 self.stylize_(style.into(), msg.into());
71 }
72
trim(&mut self)73 pub(crate) fn trim(&mut self) {
74 self.trim_start();
75 self.trim_end();
76 }
77
trim_start(&mut self)78 pub(crate) fn trim_start(&mut self) {
79 if let Some((_, item)) = self.iter_mut().next() {
80 *item = item.trim_start().to_owned();
81 }
82 }
83
84 #[cfg(feature = "color")]
trim_end(&mut self)85 pub(crate) fn trim_end(&mut self) {
86 if let Some((_, item)) = self.pieces.last_mut() {
87 *item = item.trim_end().to_owned();
88 }
89 }
90
91 #[cfg(not(feature = "color"))]
trim_end(&mut self)92 pub(crate) fn trim_end(&mut self) {
93 self.pieces = self.pieces.trim_end().to_owned();
94 }
95
96 #[cfg(feature = "help")]
indent(&mut self, initial: &str, trailing: &str)97 pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
98 if let Some((_, first)) = self.iter_mut().next() {
99 first.insert_str(0, initial);
100 }
101 let mut line_sep = "\n".to_owned();
102 line_sep.push_str(trailing);
103 for (_, content) in self.iter_mut() {
104 *content = content.replace('\n', &line_sep);
105 }
106 }
107
108 #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
wrap(&mut self, _hard_width: usize)109 pub(crate) fn wrap(&mut self, _hard_width: usize) {}
110
111 #[cfg(feature = "wrap_help")]
wrap(&mut self, hard_width: usize)112 pub(crate) fn wrap(&mut self, hard_width: usize) {
113 let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
114 for (_, content) in self.iter_mut() {
115 let mut total = Vec::new();
116 for (i, line) in content.split_inclusive('\n').enumerate() {
117 if 0 < i {
118 // start of a section does not imply newline
119 wrapper.reset();
120 }
121 let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
122 .collect::<Vec<_>>();
123 total.extend(wrapper.wrap(line));
124 }
125 let total = total.join("");
126 *content = total;
127 }
128
129 self.trim_end();
130 }
131
132 #[cfg(feature = "color")]
stylize_(&mut self, style: Option<Style>, msg: String)133 fn stylize_(&mut self, style: Option<Style>, msg: String) {
134 if !msg.is_empty() {
135 self.pieces.push((style, msg));
136 }
137 }
138
139 #[cfg(not(feature = "color"))]
stylize_(&mut self, _style: Option<Style>, msg: String)140 fn stylize_(&mut self, _style: Option<Style>, msg: String) {
141 self.pieces.push_str(&msg);
142 }
143
144 #[inline(never)]
145 #[cfg(feature = "help")]
display_width(&self) -> usize146 pub(crate) fn display_width(&self) -> usize {
147 let mut width = 0;
148 for (_, c) in self.iter() {
149 width += crate::output::display_width(c);
150 }
151 width
152 }
153
154 #[cfg(feature = "help")]
is_empty(&self) -> bool155 pub(crate) fn is_empty(&self) -> bool {
156 self.pieces.is_empty()
157 }
158
159 #[cfg(feature = "color")]
iter(&self) -> impl Iterator<Item = (Option<Style>, &str)>160 pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
161 self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
162 }
163
164 #[cfg(not(feature = "color"))]
iter(&self) -> impl Iterator<Item = (Option<Style>, &str)>165 pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
166 [(None, self.pieces.as_str())].into_iter()
167 }
168
169 #[cfg(feature = "color")]
iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)>170 pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
171 self.pieces.iter_mut().map(|(s, c)| (*s, c))
172 }
173
174 #[cfg(not(feature = "color"))]
iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)>175 pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
176 [(None, &mut self.pieces)].into_iter()
177 }
178
179 #[cfg(feature = "color")]
into_iter(self) -> impl Iterator<Item = (Option<Style>, String)>180 pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
181 self.pieces.into_iter()
182 }
183
184 #[cfg(not(feature = "color"))]
into_iter(self) -> impl Iterator<Item = (Option<Style>, String)>185 pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
186 [(None, self.pieces)].into_iter()
187 }
188
extend( &mut self, other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>, )189 pub(crate) fn extend(
190 &mut self,
191 other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
192 ) {
193 for (style, content) in other {
194 self.stylize(style.into(), content.into());
195 }
196 }
197
198 #[cfg(feature = "color")]
write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()>199 pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> {
200 use std::io::Write;
201 use termcolor::WriteColor;
202
203 for (style, content) in &self.pieces {
204 let mut color = termcolor::ColorSpec::new();
205 match style {
206 Some(Style::Header) => {
207 color.set_bold(true);
208 color.set_underline(true);
209 }
210 Some(Style::Literal) => {
211 color.set_bold(true);
212 }
213 Some(Style::Placeholder) => {}
214 Some(Style::Good) => {
215 color.set_fg(Some(termcolor::Color::Green));
216 }
217 Some(Style::Warning) => {
218 color.set_fg(Some(termcolor::Color::Yellow));
219 }
220 Some(Style::Error) => {
221 color.set_fg(Some(termcolor::Color::Red));
222 color.set_bold(true);
223 }
224 Some(Style::Hint) => {
225 color.set_dimmed(true);
226 }
227 None => {}
228 }
229
230 ok!(buffer.set_color(&color));
231 ok!(buffer.write_all(content.as_bytes()));
232 ok!(buffer.reset());
233 }
234
235 Ok(())
236 }
237 }
238
239 impl Default for &'_ StyledStr {
default() -> Self240 fn default() -> Self {
241 static DEFAULT: StyledStr = StyledStr::new();
242 &DEFAULT
243 }
244 }
245
246 impl From<std::string::String> for StyledStr {
from(name: std::string::String) -> Self247 fn from(name: std::string::String) -> Self {
248 let mut styled = StyledStr::new();
249 styled.none(name);
250 styled
251 }
252 }
253
254 impl From<&'_ std::string::String> for StyledStr {
from(name: &'_ std::string::String) -> Self255 fn from(name: &'_ std::string::String) -> Self {
256 let mut styled = StyledStr::new();
257 styled.none(name);
258 styled
259 }
260 }
261
262 impl From<&'static str> for StyledStr {
from(name: &'static str) -> Self263 fn from(name: &'static str) -> Self {
264 let mut styled = StyledStr::new();
265 styled.none(name);
266 styled
267 }
268 }
269
270 impl From<&'_ &'static str> for StyledStr {
from(name: &'_ &'static str) -> Self271 fn from(name: &'_ &'static str) -> Self {
272 StyledStr::from(*name)
273 }
274 }
275
276 impl PartialOrd for StyledStr {
partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>277 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278 Some(self.cmp(other))
279 }
280 }
281
282 impl Ord for StyledStr {
cmp(&self, other: &Self) -> std::cmp::Ordering283 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
284 self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
285 }
286 }
287
cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str)288 fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
289 let style = c.0.map(|s| s.as_usize());
290 let content = c.1;
291 (style, content)
292 }
293
294 /// Color-unaware printing. Never uses coloring.
295 impl std::fmt::Display for StyledStr {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result296 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
297 for (_, content) in self.iter() {
298 ok!(std::fmt::Display::fmt(content, f));
299 }
300
301 Ok(())
302 }
303 }
304
305 #[cfg(feature = "color")]
306 struct AnsiDisplay<'s> {
307 styled: &'s StyledStr,
308 }
309
310 #[cfg(feature = "color")]
311 impl std::fmt::Display for AnsiDisplay<'_> {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result312 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313 let mut buffer = termcolor::Buffer::ansi();
314 ok!(self
315 .styled
316 .write_colored(&mut buffer)
317 .map_err(|_| std::fmt::Error));
318 let buffer = buffer.into_inner();
319 let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error));
320 ok!(std::fmt::Display::fmt(&buffer, f));
321
322 Ok(())
323 }
324 }
325
326 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
327 pub(crate) enum Style {
328 Header,
329 Literal,
330 Placeholder,
331 Good,
332 Warning,
333 Error,
334 Hint,
335 }
336
337 impl Style {
as_usize(&self) -> usize338 fn as_usize(&self) -> usize {
339 match self {
340 Self::Header => 0,
341 Self::Literal => 1,
342 Self::Placeholder => 2,
343 Self::Good => 3,
344 Self::Warning => 4,
345 Self::Error => 5,
346 Self::Hint => 6,
347 }
348 }
349 }
350