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

Issue:
available_color_count is wrong on Windows
**Describe the bug**
Windows pseudo-TTYs support true color, yes `crossterm::style::available_color_count` reports 8. You need to query if you're in a pseudo-terminal which, BTW, means you can do just about any VT100 sequence including keyboard support, though I'm not sure what "progressive keyboard support" means.

A better detection of what colors are supported can be found at <https://github.com/cli/go-gh/blob/dbd982eba2a041d88d398cce01cb708c4c3503f7/pkg/term/env.go#L70-L72>. I worked on that for the GitHub CLI (was refactored into cli/go-gh from cli/cli) along with colleagues on the Windows terminal team that own conhost and conpty - both of which support VT100 sequences since some version of Windows 10 (TH2, maybe? Anything older isn't supported anyway.)

**To Reproduce**
Steps to reproduce the behavior:
1. Write a simple bin:
   ```rust
   fn main() {
       println!("{}", crossterm::style::available_color_count());
   }
   ```
3. Run `cargo run`

**Expected behavior**
`256` is printed to stdout.

**OS**
 - Windows 11

**Terminal/Console**
 - Windows Terminal; though, conhost.exe supports this - which cmd.exe and powershell.exe/pwsh.exe use by default unless you default to wt.exe, which uses conpty.exe. Effectively, conpty is dead on built-in terminals and shells.



Discussion / hints:
For that matter, just checking `TERM` is not sufficient on any platform. See helper functions used by the cli/go-ch link above: <https://github.com/cli/go-gh/blob/dbd982eba2a041d88d398cce01cb708c4c3503f7/pkg/term/env.go#L170-L184>.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2c53c14be..68ca5df18 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -89,6 +89,7 @@ futures-timer = "3.0"
 async-std = "1.12"
 serde_json = "1.0"
 serial_test = "2.0.0"
+temp-env = "0.3.6"
 
 #
 # Examples
diff --git a/src/style.rs b/src/style.rs
index a2618365c..b67a421b1 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -161,9 +161,23 @@ pub fn style<D: Display>(val: D) -> StyledContent<D> {
 ///
 /// This does not always provide a good result.
 pub fn available_color_count() -> u16 {
-    env::var("TERM")
-        .map(|x| if x.contains("256color") { 256 } else { 8 })
-        .unwrap_or(8)
+    #[cfg(windows)]
+    {
+        // Check if we're running in a pseudo TTY, which supports true color.
+        // Fall back to env vars otherwise for other terminals on Windows.
+        if crate::ansi_support::supports_ansi() {
+            return u16::MAX;
+        }
+    }
+
+    const DEFAULT: u16 = 8;
+    env::var("COLORTERM")
+        .or_else(|_| env::var("TERM"))
+        .map_or(DEFAULT, |x| match x {
+            _ if x.contains("24bit") || x.contains("truecolor") => u16::MAX,
+            _ if x.contains("256") => 256,
+            _ => DEFAULT,
+        })
 }
 
 /// Forces colored output on or off globally, overriding NO_COLOR.
@@ -519,3 +533,89 @@ impl_display!(for ResetColor);
 fn parse_next_u8<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Option<u8> {
     iter.next().and_then(|s| s.parse().ok())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // On Windows many env var tests will fail so we need to conditionally check for ANSI support.
+    // This allows other terminals on Windows to still assert env var support.
+    macro_rules! skip_windows_ansi_supported {
+        () => {
+            #[cfg(windows)]
+            {
+                if crate::ansi_support::supports_ansi() {
+                    return;
+                }
+            }
+        };
+    }
+
+    #[cfg_attr(windows, test)]
+    #[cfg(windows)]
+    fn windows_always_truecolor() {
+        // This should always be true on supported Windows 10+,
+        // but downlevel Windows clients and other terminals may fail `cargo test` otherwise.
+        if crate::ansi_support::supports_ansi() {
+            assert_eq!(u16::MAX, available_color_count());
+        };
+    }
+
+    #[test]
+    fn colorterm_overrides_term() {
+        skip_windows_ansi_supported!();
+        temp_env::with_vars(
+            [
+                ("COLORTERM", Some("truecolor")),
+                ("TERM", Some("xterm-256color")),
+            ],
+            || {
+                assert_eq!(u16::MAX, available_color_count());
+            },
+        );
+    }
+
+    #[test]
+    fn term_24bits() {
+        skip_windows_ansi_supported!();
+        temp_env::with_vars(
+            [("COLORTERM", None), ("TERM", Some("xterm-24bits"))],
+            || {
+                assert_eq!(u16::MAX, available_color_count());
+            },
+        );
+    }
+
+    #[test]
+    fn term_256color() {
+        skip_windows_ansi_supported!();
+        temp_env::with_vars(
+            [("COLORTERM", None), ("TERM", Some("xterm-256color"))],
+            || {
+                assert_eq!(256u16, available_color_count());
+            },
+        );
+    }
+
+    #[test]
+    fn default_color_count() {
+        skip_windows_ansi_supported!();
+        temp_env::with_vars([("COLORTERM", None::<&str>), ("TERM", None)], || {
+            assert_eq!(8, available_color_count());
+        });
+    }
+
+    #[test]
+    fn unsupported_term_colorterm_values() {
+        skip_windows_ansi_supported!();
+        temp_env::with_vars(
+            [
+                ("COLORTERM", Some("gibberish")),
+                ("TERM", Some("gibberish")),
+            ],
+            || {
+                assert_eq!(8u16, available_color_count());
+            },
+        );
+    }
+}


Test patch: