Repository: clap-rs/clap (language: rust)

Issue:
Allow styled text in `template`
<!--
Please use the following template to assist with creating an issue and to ensure a speedy resolution. If an area is not applicable, feel free to delete the area or mark with `N/A`
-->

### Rust Version

`rustc 1.32.0 (9fda7c223 2019-01-16)`

### Affected Version of clap

2.32.0

### Bug or Feature Request Summary

When using a custom template to work around papercuts in Clap (#1432) and StructOpt (TeXitoi/structopt#172) which break `help2man`, i can find no documented way to apply `AppSettings::ColoredHelp` to the "USAGE:" header, though I've managed to reconstruct the desired bits of all of the rest of the default template.

### Expected Behavior Summary

I should have some means of reintroducing the conditionally and portably colored `USAGE:` header, like this:

![desired](https://user-images.githubusercontent.com/46915/54571592-61dc3e00-49db-11e9-94cc-d1b09d698bae.png)

**NOTE:** Image created as a composite of two screenshots.

### Actual Behavior Summary

I was able to recreate every desired element of the default template *except* the coloring on the `USAGE:` header:

![actual](https://user-images.githubusercontent.com/46915/54571565-3d806180-49db-11e9-823b-55048f4436fc.png)

### Steps to Reproduce the issue

1. Copy the example from the README into a new project
2. Use the following custom template in concert with `AppSettings::ColoredHelp`:

```rust
const HELP_TEMPLATE: &str = "{bin} {version}

{about}

USAGE:
    {usage}

{all-args}
";
```

### Sample Code or Link to Sample Code

https://gist.github.com/ssokolow/9df8c92b24d94116c5eb57fccad2b1a2


Discussion / hints:
...and, aesthetics aside, having a `USAGE:` header is necessary for `help2man` to recognize the usage line.
Equally, if you use, as a guideline, the "full help template" that can be found here:

https://github.com/clap-rs/clap/blob/master/tests/example1_tmpl_full.txt

...there is no way to apply the ColoredHelp formatting to the various other headings if, for instance, you wish to change their order or rename them (e.g. for language translations).

Unsure at this point of the best approach to getting coloured help output when using custom help templates.
**EDIT:** Sorry if you saw the original form of this via e-mail notification. I initially misunderstood the intent of your response.

True, but, In my testing, aside from the USAGE: header not being colourized, the template I gave is equivalent to removing the author placeholder from the default template and inserting a newline before `{about}` so `help2man` understands it properly.

`example1_tmpl_full.txt` has the additional problem of not replicating the "hide headers for empty sections" behaviour of the default template.

That said, what I'd suggest is some kind of placeholder that resolves to either changing the colour if colourization is enabled or nothing if it's disabled.
> That said, what I'd suggest is some kind of placeholder that resolves to either changing the colour if colourization is enabled or nothing if it's disabled.

I was thinking along these lines in the context of https://github.com/clap-rs/clap/issues/1790. Something like
```
{color:yellow:USAGE}
```

or more generally
```
{color:color_name-bold:text}
 ----- ---------- ---- ----
 |        |         |
 keyword  |       optional bold switch
       the color
```
And maintain the list of supported colors. 

I don't want to support any RGB-or-something notations because Windows CMD has only a set of predefined colors and implementing the RGB code => winapi color mapping is something I very much like to avoid.


The original `usage` issue is fixed in #2131. I am repurposing this issue for general styled text in templates.
My plan is for the API to accept ANSI colored strings.  When we output, we use a special stdout/stderr that adapt the ANSI color codes to the capabilities of the terminal.

Blocked on https://github.com/epage/anstyle/issues/5

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7e3ead759c2..6d3fa11539d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -25,9 +25,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.2.3"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd0982309face56a044e935a18bbffcddeb1ce72e69a3ecc3bafb56d4e959f37"
+checksum = "2abc612600451a4beeff27bf046474b29f7eab30b15846975949f30f9e54afad"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -40,9 +40,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "0.3.3"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "453bc2a7b261f8c4d1ce5b2c6c222d648d00988d30315e4911fbddc4ddf8983c"
+checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2"
 
 [[package]]
 name = "anstyle-parse"
@@ -203,18 +203,19 @@ dependencies = [
 name = "clap_builder"
 version = "4.1.14"
 dependencies = [
+ "anstream",
+ "anstyle",
  "backtrace",
  "bitflags",
  "clap_lex 0.4.0",
+ "color-print",
  "humantime",
- "is-terminal",
  "once_cell",
  "rustversion",
  "shlex",
  "snapbox",
  "static_assertions",
  "strsim",
- "termcolor",
  "terminal_size",
  "trybuild",
  "trycmd",
@@ -278,6 +279,27 @@ dependencies = [
  "snapbox",
 ]
 
+[[package]]
+name = "color-print"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2a5e6504ed8648554968650feecea00557a3476bc040d0ffc33080e66b646d0"
+dependencies = [
+ "color-print-proc-macro",
+]
+
+[[package]]
+name = "color-print-proc-macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51beaa537d73d2d1ff34ee70bc095f170420ab2ec5d687ecd3ec2b0d092514b"
+dependencies = [
+ "nom",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "concolor-override"
 version = "1.0.0"
@@ -579,6 +601,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
 [[package]]
 name = "miniz_oxide"
 version = "0.6.2"
@@ -588,6 +616,16 @@ dependencies = [
  "adler",
 ]
 
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
 [[package]]
 name = "normalize-line-endings"
 version = "0.3.0"
diff --git a/clap_builder/Cargo.toml b/clap_builder/Cargo.toml
index 6c83fb598f5..85f5bb765dc 100644
--- a/clap_builder/Cargo.toml
+++ b/clap_builder/Cargo.toml
@@ -30,20 +30,13 @@ dependent-version = "upgrade"
 tag-name = "v{{version}}"
 
 [features]
-default = [
-  "std",
-  "color",
-  "help",
-  "usage",
-  "error-context",
-  "suggestions",
-]
+default = ["std", "color", "help", "usage", "error-context", "suggestions"]
 debug = ["dep:backtrace"] # Enables debug messages
 unstable-doc = ["cargo", "wrap_help", "env", "unicode", "string", "unstable-replace"] # for docs.rs
 
 # Used in default
 std = [] # support for no_std in a backwards-compatible way
-color = ["dep:is-terminal", "dep:termcolor"]
+color = ["dep:anstyle", "dep:anstream"]
 help = []
 usage = []
 error-context = []
@@ -70,8 +63,8 @@ clap_lex = { path = "../clap_lex", version = "0.4.0" }
 bitflags = "1.2.0"
 unicase = { version = "2.6.0", optional = true }
 strsim = { version = "0.10.0",  optional = true }
-is-terminal = { version = "0.4.1",  optional = true }
-termcolor = { version = "1.1.1", optional = true }
+anstream = { version = "0.2.5", optional = true }
+anstyle = { version = "0.3.5", features = ["std"], optional = true }
 terminal_size = { version = "0.2.1", optional = true }
 backtrace = { version = "0.3.67", optional = true }
 unicode-width = { version = "0.1.9", optional = true }
@@ -87,3 +80,4 @@ snapbox = "0.4.10"
 shlex = "1.1.0"
 static_assertions = "1.1.0"
 unic-emoji-char = "0.9.0"
+color-print = "0.3.4"
diff --git a/clap_builder/src/builder/arg.rs b/clap_builder/src/builder/arg.rs
index 577d968267c..574e6fbaf2f 100644
--- a/clap_builder/src/builder/arg.rs
+++ b/clap_builder/src/builder/arg.rs
@@ -4280,7 +4280,7 @@ impl Arg {
             styled.literal("-");
             styled.literal(s);
         }
-        styled.extend(self.stylize_arg_suffix(required).into_iter());
+        styled.push_styled(&self.stylize_arg_suffix(required));
         styled
     }
 
diff --git a/clap_builder/src/builder/command.rs b/clap_builder/src/builder/command.rs
index acbfae28cdd..8f62c2c1f0b 100644
--- a/clap_builder/src/builder/command.rs
+++ b/clap_builder/src/builder/command.rs
@@ -4673,9 +4673,7 @@ impl Command {
 
     pub(crate) fn write_version_err(&self, use_long: bool) -> StyledStr {
         let msg = self._render_version(use_long);
-        let mut styled = StyledStr::new();
-        styled.none(msg);
-        styled
+        StyledStr::from(msg)
     }
 
     pub(crate) fn long_help_exists(&self) -> bool {
diff --git a/clap_builder/src/builder/styled_str.rs b/clap_builder/src/builder/styled_str.rs
index d36329dee3f..94c838aaabc 100644
--- a/clap_builder/src/builder/styled_str.rs
+++ b/clap_builder/src/builder/styled_str.rs
@@ -1,108 +1,89 @@
 /// Terminal-styling container
 ///
-/// For now, this is the same as a [`Str`][crate::builder::Str].  This exists to reserve space in
-/// the API for exposing terminal styling.
-#[derive(Clone, Default, Debug, PartialEq, Eq)]
-pub struct StyledStr {
-    #[cfg(feature = "color")]
-    pieces: Vec<(Option<Style>, String)>,
-    #[cfg(not(feature = "color"))]
-    pieces: String,
-}
+/// Styling may be encoded as [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code)
+///
+/// # Examples
+///
+/// ```rust
+/// # use clap_builder as clap;
+/// // `cstr!` converts tags to ANSI codes
+/// let after_help: &'static str = color_print::cstr!(
+/// r#"<bold><underline>Examples</underline></bold>
+///
+///   <dim>$</dim> <bold>mybin --input file.toml</bold>
+/// "#);
+///
+/// let cmd = clap::Command::new("mybin")
+///     .after_help(after_help)  // The `&str` gets converted into a `StyledStr`
+///     // ...
+/// #   ;
+/// ```
+#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct StyledStr(String);
 
 impl StyledStr {
     /// Create an empty buffer
-    #[cfg(feature = "color")]
-    pub const fn new() -> Self {
-        Self { pieces: Vec::new() }
-    }
-
-    /// Create an empty buffer
-    #[cfg(not(feature = "color"))]
     pub const fn new() -> Self {
-        Self {
-            pieces: String::new(),
-        }
+        Self(String::new())
     }
 
     /// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
     #[cfg(feature = "color")]
     pub fn ansi(&self) -> impl std::fmt::Display + '_ {
-        AnsiDisplay { styled: self }
+        self.0.as_str()
     }
 
     pub(crate) fn header(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Header), msg.into());
+        self.stylize(Style::Header, msg.into());
     }
 
     pub(crate) fn literal(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Literal), msg.into());
+        self.stylize(Style::Literal, msg.into());
     }
 
     pub(crate) fn placeholder(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Placeholder), msg.into());
+        self.stylize(Style::Placeholder, msg.into());
     }
 
     #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
     pub(crate) fn good(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Good), msg.into());
+        self.stylize(Style::Good, msg.into());
     }
 
     #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
     pub(crate) fn warning(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Warning), msg.into());
+        self.stylize(Style::Warning, msg.into());
     }
 
     pub(crate) fn error(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Error), msg.into());
+        self.stylize(Style::Error, msg.into());
     }
 
     #[allow(dead_code)]
     pub(crate) fn hint(&mut self, msg: impl Into<String>) {
-        self.stylize_(Some(Style::Hint), msg.into());
+        self.stylize(Style::Hint, msg.into());
     }
 
     pub(crate) fn none(&mut self, msg: impl Into<String>) {
-        self.stylize_(None, msg.into());
-    }
-
-    pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
-        self.stylize_(style.into(), msg.into());
+        self.0.push_str(&msg.into());
     }
 
     pub(crate) fn trim(&mut self) {
-        self.trim_start();
-        self.trim_end();
-    }
-
-    pub(crate) fn trim_start(&mut self) {
-        if let Some((_, item)) = self.iter_mut().next() {
-            *item = item.trim_start().to_owned();
-        }
-    }
-
-    #[cfg(feature = "color")]
-    pub(crate) fn trim_end(&mut self) {
-        if let Some((_, item)) = self.pieces.last_mut() {
-            *item = item.trim_end().to_owned();
-        }
+        self.0 = self.0.trim().to_owned()
     }
 
-    #[cfg(not(feature = "color"))]
-    pub(crate) fn trim_end(&mut self) {
-        self.pieces = self.pieces.trim_end().to_owned();
+    #[cfg(feature = "help")]
+    pub(crate) fn replace_newline_var(&mut self) {
+        self.0 = self.0.replace("{n}", "\n");
     }
 
     #[cfg(feature = "help")]
     pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
-        if let Some((_, first)) = self.iter_mut().next() {
-            first.insert_str(0, initial);
-        }
+        self.0.insert_str(0, initial);
+
         let mut line_sep = "\n".to_owned();
         line_sep.push_str(trailing);
-        for (_, content) in self.iter_mut() {
-            *content = content.replace('\n', &line_sep);
-        }
+        self.0 = self.0.replace('\n', &line_sep);
     }
 
     #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
@@ -110,42 +91,57 @@ impl StyledStr {
 
     #[cfg(feature = "wrap_help")]
     pub(crate) fn wrap(&mut self, hard_width: usize) {
+        let mut new = String::with_capacity(self.0.len());
+
+        let mut last = 0;
         let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
-        for (_, content) in self.iter_mut() {
-            let mut total = Vec::new();
+        for content in self.iter_text() {
+            // Preserve styling
+            let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
+            if last != current {
+                new.push_str(&self.0.as_str()[last..current]);
+            }
+            last = current + content.len();
+
             for (i, line) in content.split_inclusive('\n').enumerate() {
                 if 0 < i {
-                    // start of a section does not imply newline
+                    // reset char count on newline, skipping the start as we might have carried
+                    // over from a prior block of styled text
                     wrapper.reset();
                 }
                 let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
                     .collect::<Vec<_>>();
-                total.extend(wrapper.wrap(line));
+                new.extend(wrapper.wrap(line));
             }
-            let total = total.join("");
-            *content = total;
         }
+        if last != self.0.len() {
+            new.push_str(&self.0.as_str()[last..]);
+        }
+        new = new.trim_end().to_owned();
 
-        self.trim_end();
+        self.0 = new;
     }
 
     #[cfg(feature = "color")]
-    fn stylize_(&mut self, style: Option<Style>, msg: String) {
+    fn stylize(&mut self, style: Style, msg: String) {
         if !msg.is_empty() {
-            self.pieces.push((style, msg));
+            use std::fmt::Write as _;
+
+            let style = style.as_style();
+            let _ = write!(self.0, "{}{}{}", style.render(), msg, style.render_reset());
         }
     }
 
     #[cfg(not(feature = "color"))]
-    fn stylize_(&mut self, _style: Option<Style>, msg: String) {
-        self.pieces.push_str(&msg);
+    fn stylize(&mut self, _style: Style, msg: String) {
+        self.0.push_str(&msg);
     }
 
     #[inline(never)]
     #[cfg(feature = "help")]
     pub(crate) fn display_width(&self) -> usize {
         let mut width = 0;
-        for (_, c) in self.iter() {
+        for c in self.iter_text() {
             width += crate::output::display_width(c);
         }
         width
@@ -153,84 +149,30 @@ impl StyledStr {
 
     #[cfg(feature = "help")]
     pub(crate) fn is_empty(&self) -> bool {
-        self.pieces.is_empty()
-    }
-
-    #[cfg(feature = "color")]
-    pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
-        self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
+        self.0.is_empty()
     }
 
-    #[cfg(not(feature = "color"))]
-    pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
-        [(None, self.pieces.as_str())].into_iter()
-    }
-
-    #[cfg(feature = "color")]
-    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
-        self.pieces.iter_mut().map(|(s, c)| (*s, c))
-    }
-
-    #[cfg(not(feature = "color"))]
-    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
-        [(None, &mut self.pieces)].into_iter()
+    #[cfg(feature = "help")]
+    pub(crate) fn as_styled_str(&self) -> &str {
+        &self.0
     }
 
     #[cfg(feature = "color")]
-    pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
-        self.pieces.into_iter()
+    pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
+        anstream::adapter::strip_str(&self.0)
     }
 
     #[cfg(not(feature = "color"))]
-    pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
-        [(None, self.pieces)].into_iter()
+    pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
+        [self.0.as_str()].into_iter()
     }
 
-    pub(crate) fn extend(
-        &mut self,
-        other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
-    ) {
-        for (style, content) in other {
-            self.stylize(style.into(), content.into());
-        }
+    pub(crate) fn push_styled(&mut self, other: &Self) {
+        self.0.push_str(&other.0);
     }
 
-    #[cfg(feature = "color")]
-    pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> {
-        use std::io::Write;
-        use termcolor::WriteColor;
-
-        for (style, content) in &self.pieces {
-            let mut color = termcolor::ColorSpec::new();
-            match style {
-                Some(Style::Header) => {
-                    color.set_bold(true);
-                    color.set_underline(true);
-                }
-                Some(Style::Literal) => {
-                    color.set_bold(true);
-                }
-                Some(Style::Placeholder) => {}
-                Some(Style::Good) => {
-                    color.set_fg(Some(termcolor::Color::Green));
-                }
-                Some(Style::Warning) => {
-                    color.set_fg(Some(termcolor::Color::Yellow));
-                }
-                Some(Style::Error) => {
-                    color.set_fg(Some(termcolor::Color::Red));
-                    color.set_bold(true);
-                }
-                Some(Style::Hint) => {
-                    color.set_dimmed(true);
-                }
-                None => {}
-            }
-
-            ok!(buffer.set_color(&color));
-            ok!(buffer.write_all(content.as_bytes()));
-            ok!(buffer.reset());
-        }
+    pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
+        ok!(buffer.write_all(self.0.as_bytes()));
 
         Ok(())
     }
@@ -245,9 +187,7 @@ impl Default for &'_ StyledStr {
 
 impl From<std::string::String> for StyledStr {
     fn from(name: std::string::String) -> Self {
-        let mut styled = StyledStr::new();
-        styled.none(name);
-        styled
+        StyledStr(name)
     }
 }
 
@@ -273,56 +213,31 @@ impl From<&'_ &'static str> for StyledStr {
     }
 }
 
-impl PartialOrd for StyledStr {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
+impl std::fmt::Write for StyledStr {
+    #[inline]
+    fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
+        self.0.push_str(s);
+        Ok(())
     }
-}
 
-impl Ord for StyledStr {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
+    #[inline]
+    fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
+        self.0.push(c);
+        Ok(())
     }
 }
 
-fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
-    let style = c.0.map(|s| s.as_usize());
-    let content = c.1;
-    (style, content)
-}
-
 /// Color-unaware printing. Never uses coloring.
 impl std::fmt::Display for StyledStr {
     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        for (_, content) in self.iter() {
-            ok!(std::fmt::Display::fmt(content, f));
+        for part in self.iter_text() {
+            part.fmt(f)?;
         }
 
         Ok(())
     }
 }
 
-#[cfg(feature = "color")]
-struct AnsiDisplay<'s> {
-    styled: &'s StyledStr,
-}
-
-#[cfg(feature = "color")]
-impl std::fmt::Display for AnsiDisplay<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        let mut buffer = termcolor::Buffer::ansi();
-        ok!(self
-            .styled
-            .write_colored(&mut buffer)
-            .map_err(|_| std::fmt::Error));
-        let buffer = buffer.into_inner();
-        let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error));
-        ok!(std::fmt::Display::fmt(&buffer, f));
-
-        Ok(())
-    }
-}
-
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Style {
     Header,
@@ -335,15 +250,16 @@ pub(crate) enum Style {
 }
 
 impl Style {
-    fn as_usize(&self) -> usize {
+    #[cfg(feature = "color")]
+    fn as_style(&self) -> anstyle::Style {
         match self {
-            Self::Header => 0,
-            Self::Literal => 1,
-            Self::Placeholder => 2,
-            Self::Good => 3,
-            Self::Warning => 4,
-            Self::Error => 5,
-            Self::Hint => 6,
+            Style::Header => (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).into(),
+            Style::Literal => anstyle::Effects::BOLD.into(),
+            Style::Placeholder => anstyle::Style::default(),
+            Style::Good => anstyle::AnsiColor::Green.on_default(),
+            Style::Warning => anstyle::AnsiColor::Yellow.on_default(),
+            Style::Error => anstyle::AnsiColor::Red.on_default() | anstyle::Effects::BOLD,
+            Style::Hint => anstyle::Effects::DIMMED.into(),
         }
     }
 }
diff --git a/clap_builder/src/error/format.rs b/clap_builder/src/error/format.rs
index f399cd01e46..7af1fd0c0c0 100644
--- a/clap_builder/src/error/format.rs
+++ b/clap_builder/src/error/format.rs
@@ -100,13 +100,13 @@ impl ErrorFormatter for RichFormatter {
                 styled.none("\n");
                 styled.none(TAB);
                 styled.good("note: ");
-                styled.extend(suggestion.iter());
+                styled.push_styled(suggestion);
             }
         }
 
         let usage = error.get(ContextKind::Usage);
         if let Some(ContextValue::StyledStr(usage)) = usage {
-            put_usage(&mut styled, usage.clone());
+            put_usage(&mut styled, usage);
         }
 
         try_help(&mut styled, error.inner.help_flag);
@@ -377,7 +377,7 @@ fn write_dynamic_context(error: &crate::error::Error, styled: &mut StyledStr) ->
 pub(crate) fn format_error_message(
     message: &str,
     cmd: Option<&Command>,
-    usage: Option<StyledStr>,
+    usage: Option<&StyledStr>,
 ) -> StyledStr {
     let mut styled = StyledStr::new();
     start_error(&mut styled);
@@ -400,9 +400,9 @@ fn singular_or_plural(n: usize) -> &'static str {
     }
 }
 
-fn put_usage(styled: &mut StyledStr, usage: StyledStr) {
+fn put_usage(styled: &mut StyledStr, usage: &StyledStr) {
     styled.none("\n\n");
-    styled.extend(usage.into_iter());
+    styled.push_styled(usage);
 }
 
 pub(crate) fn get_help_flag(cmd: &Command) -> Option<&'static str> {
diff --git a/clap_builder/src/error/mod.rs b/clap_builder/src/error/mod.rs
index adbe183fe98..52f17bd5649 100644
--- a/clap_builder/src/error/mod.rs
+++ b/clap_builder/src/error/mod.rs
@@ -806,7 +806,7 @@ impl Message {
                 let mut message = String::new();
                 std::mem::swap(s, &mut message);
 
-                let styled = format::format_error_message(&message, Some(cmd), usage);
+                let styled = format::format_error_message(&message, Some(cmd), usage.as_ref());
 
                 *self = Self::Formatted(styled);
             }
diff --git a/clap_builder/src/output/fmt.rs b/clap_builder/src/output/fmt.rs
index b5d0153f11a..0c9a24f33a2 100644
--- a/clap_builder/src/output/fmt.rs
+++ b/clap_builder/src/output/fmt.rs
@@ -34,40 +34,42 @@ impl Colorizer {
 impl Colorizer {
     #[cfg(feature = "color")]
     pub(crate) fn print(&self) -> std::io::Result<()> {
-        use termcolor::{BufferWriter, ColorChoice as DepColorChoice};
-
         let color_when = match self.color_when {
-            ColorChoice::Always => DepColorChoice::Always,
-            ColorChoice::Auto if is_a_tty(self.stream) => DepColorChoice::Auto,
-            _ => DepColorChoice::Never,
+            ColorChoice::Always => anstream::ColorChoice::Always,
+            ColorChoice::Auto => anstream::ColorChoice::Auto,
+            ColorChoice::Never => anstream::ColorChoice::Never,
         };
 
-        let writer = match self.stream {
-            Stream::Stderr => BufferWriter::stderr(color_when),
-            Stream::Stdout => BufferWriter::stdout(color_when),
+        let mut stdout;
+        let mut stderr;
+        let writer: &mut dyn std::io::Write = match self.stream {
+            Stream::Stderr => {
+                stderr = anstream::AutoStream::new(std::io::stderr().lock(), color_when);
+                &mut stderr
+            }
+            Stream::Stdout => {
+                stdout = anstream::AutoStream::new(std::io::stdout().lock(), color_when);
+                &mut stdout
+            }
         };
 
-        let mut buffer = writer.buffer();
-        ok!(self.content.write_colored(&mut buffer));
-        writer.print(&buffer)
+        self.content.write_to(writer)
     }
 
     #[cfg(not(feature = "color"))]
     pub(crate) fn print(&self) -> std::io::Result<()> {
-        use std::io::Write;
-
         // [e]println can't be used here because it panics
         // if something went wrong. We don't want that.
         match self.stream {
             Stream::Stdout => {
                 let stdout = std::io::stdout();
                 let mut stdout = stdout.lock();
-                write!(stdout, "{}", self)
+                self.content.write_to(&mut stdout)
             }
             Stream::Stderr => {
                 let stderr = std::io::stderr();
                 let mut stderr = stderr.lock();
-                write!(stderr, "{}", self)
+                self.content.write_to(&mut stderr)
             }
         }
     }
@@ -79,12 +81,3 @@ impl std::fmt::Display for Colorizer {
         self.content.fmt(f)
     }
 }
-
-#[cfg(feature = "color")]
-fn is_a_tty(stream: Stream) -> bool {
-    use is_terminal::IsTerminal;
-    match stream {
-        Stream::Stdout => std::io::stdout().is_terminal(),
-        Stream::Stderr => std::io::stderr().is_terminal(),
-    }
-}
diff --git a/clap_builder/src/output/help.rs b/clap_builder/src/output/help.rs
index 4e4fdbfdcaa..4921f5f71f3 100644
--- a/clap_builder/src/output/help.rs
+++ b/clap_builder/src/output/help.rs
@@ -10,21 +10,15 @@ pub(crate) fn write_help(writer: &mut StyledStr, cmd: &Command, usage: &Usage<'_
     debug!("write_help");
 
     if let Some(h) = cmd.get_override_help() {
-        writer.extend(h.iter());
+        writer.push_styled(h);
     } else {
         #[cfg(feature = "help")]
         {
             use super::AutoHelp;
             use super::HelpTemplate;
             if let Some(tmpl) = cmd.get_help_template() {
-                for (style, content) in tmpl.iter() {
-                    if style.is_none() {
-                        HelpTemplate::new(writer, cmd, usage, use_long)
-                            .write_templated_help(content);
-                    } else {
-                        writer.stylize(style, content);
-                    }
-                }
+                HelpTemplate::new(writer, cmd, usage, use_long)
+                    .write_templated_help(tmpl.as_styled_str());
             } else {
                 AutoHelp::new(writer, cmd, usage, use_long).write_help();
             }
diff --git a/clap_builder/src/output/help_template.rs b/clap_builder/src/output/help_template.rs
index 606dccbfefe..cea9a7687bb 100644
--- a/clap_builder/src/output/help_template.rs
+++ b/clap_builder/src/output/help_template.rs
@@ -166,11 +166,8 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
                         self.header("Usage:");
                     }
                     "usage" => {
-                        self.writer.extend(
-                            self.usage
-                                .create_usage_no_title(&[])
-                                .unwrap_or_default()
-                                .into_iter(),
+                        self.writer.push_styled(
+                            &self.usage.create_usage_no_title(&[]).unwrap_or_default(),
                         );
                     }
                     "all-args" => {
@@ -284,9 +281,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
                 self.none("\n");
             }
             let mut output = output.clone();
-            replace_newline_var(&mut output);
+            output.replace_newline_var();
             output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
+            self.writer.push_styled(&output);
             if after_new_line {
                 self.none("\n");
             }
@@ -304,9 +301,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         };
         if let Some(output) = before_help {
             let mut output = output.clone();
-            replace_newline_var(&mut output);
+            output.replace_newline_var();
             output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
+            self.writer.push_styled(&output);
             self.none("\n\n");
         }
     }
@@ -323,9 +320,9 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         if let Some(output) = after_help {
             self.none("\n\n");
             let mut output = output.clone();
-            replace_newline_var(&mut output);
+            output.replace_newline_var();
             output.wrap(self.term_w);
-            self.writer.extend(output.into_iter());
+            self.writer.push_styled(&output);
         }
     }
 }
@@ -470,7 +467,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         self.none(TAB);
         self.short(arg);
         self.long(arg);
-        self.writer.extend(arg.stylize_arg_suffix(None).into_iter());
+        self.writer.push_styled(&arg.stylize_arg_suffix(None));
         self.align_to_about(arg, next_line_help, longest);
 
         let about = if self.use_long {
@@ -580,7 +577,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         let trailing_indent = self.get_spaces(trailing_indent);
 
         let mut help = about.clone();
-        replace_newline_var(&mut help);
+        help.replace_newline_var();
         if !spec_vals.is_empty() {
             if !help.is_empty() {
                 let sep = if self.use_long && arg.is_some() {
@@ -602,7 +599,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         help.wrap(avail_chars);
         help.indent("", &trailing_indent);
         let help_is_empty = help.is_empty();
-        self.writer.extend(help.into_iter());
+        self.writer.push_styled(&help);
         if let Some(arg) = arg {
             const DASH_SPACE: usize = "- ".len();
             const COLON_SPACE: usize = ": ".len();
@@ -668,10 +665,10 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
                         };
 
                         let mut help = help.clone();
-                        replace_newline_var(&mut help);
+                        help.replace_newline_var();
                         help.wrap(avail_chars);
                         help.indent("", &trailing_indent);
-                        self.writer.extend(help.into_iter());
+                        self.writer.push_styled(&help);
                     }
                 }
             }
@@ -948,7 +945,7 @@ impl<'cmd, 'writer> HelpTemplate<'cmd, 'writer> {
         let width = sc_str.display_width();
 
         self.none(TAB);
-        self.writer.extend(sc_str.into_iter());
+        self.writer.push_styled(&sc_str);
         if !next_line_help {
             self.spaces(longest + TAB_WIDTH - width);
         }
@@ -1021,12 +1018,6 @@ fn should_show_subcommand(subcommand: &Command) -> bool {
     !subcommand.is_hide_set()
 }
 
-fn replace_newline_var(styled: &mut StyledStr) {
-    for (_, content) in styled.iter_mut() {
-        *content = content.replace("{n}", "\n");
-    }
-}
-
 fn longest_filter(arg: &Arg) -> bool {
     arg.is_takes_value_set() || arg.get_long().is_some() || arg.get_short().is_none()
 }
diff --git a/clap_builder/src/output/usage.rs b/clap_builder/src/output/usage.rs
index 145fce12d08..c49ca2cb063 100644
--- a/clap_builder/src/output/usage.rs
+++ b/clap_builder/src/output/usage.rs
@@ -40,7 +40,7 @@ impl<'cmd> Usage<'cmd> {
         let mut styled = StyledStr::new();
         styled.header("Usage:");
         styled.none(" ");
-        styled.extend(usage.into_iter());
+        styled.push_styled(&usage);
         Some(styled)
     }
 
@@ -103,7 +103,7 @@ impl<'cmd> Usage<'cmd> {
                     // Short-circuit full usage creation since no args will be relevant
                     styled.literal(name);
                 } else {
-                    styled.extend(self.create_help_usage(false).into_iter());
+                    styled.push_styled(&self.create_help_usage(false));
                 }
                 styled.placeholder(" <");
                 styled.placeholder(placeholder);
@@ -190,7 +190,7 @@ impl<'cmd> Usage<'cmd> {
     pub(crate) fn write_args(&self, incls: &[Id], force_optional: bool, styled: &mut StyledStr) {
         for required in self.get_args(incls, force_optional) {
             styled.none(" ");
-            styled.extend(required.into_iter());
+            styled.push_styled(&required);
         }
     }
 
@@ -280,7 +280,7 @@ impl<'cmd> Usage<'cmd> {
                     let styled = required_positionals[index].take().unwrap();
                     let mut new = StyledStr::new();
                     new.literal("-- ");
-                    new.extend(styled.into_iter());
+                    new.push_styled(&styled);
                     required_positionals[index] = Some(new);
                 }
             } else {
@@ -288,7 +288,7 @@ impl<'cmd> Usage<'cmd> {
                 if pos.is_last_set() {
                     styled = StyledStr::new();
                     styled.literal("[-- ");
-                    styled.extend(pos.stylized(Some(true)).into_iter());
+                    styled.push_styled(&pos.stylized(Some(true)));
                     styled.literal("]");
                 } else {
                     styled = pos.stylized(Some(false));


Test patch: