1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
use crate::chunk::File;
use anyhow::Result;
use std::env;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TextWrapMode {
Char,
Never,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum TermColorSupport {
True,
Ansi256,
Ansi16,
}
impl TermColorSupport {
fn detect_from_colorterm() -> Option<Self> {
// > The existence of this variable signifies extra colour capabilities of some sort. If it has the value “truecolor”
// > or the value “24bit” then the terminal is taken to understand ISO 8613-6/ITU T.416 Direct colour SGR 38 and SGR
// > 48 control sequences. Otherwise it indicates that the terminal understands the additional 8 (de facto) standard
// > colours (from AIXTerm) set by SGR 90–97 and SGR 100–107.
//
// http://jdebp.uk/Softwares/nosh/guide/TerminalCapabilities.html
env::var("COLORTERM").ok().map(|v| {
if v.eq_ignore_ascii_case("truecolor") || v.eq_ignore_ascii_case("24bit") {
Self::True
} else {
Self::Ansi16
}
})
}
// > The TERM environment variable is its primary source of information. Its value is expected to follow the terminfo(5) and
// > termcap(5) convention (laid out in term(7)) of a root name followed by zero or more hyphen-separated feature suffixes
// > (such as, for example, “teken-256color”). The TerminalCapabilities class only cares about the root name, which it takes
// > to denote a family of terminal types (e.g. “putty”), and whether there are “-24bit”, “-truecolor”, “-256color”, or
// > “-square” suffixes in the value. Other feature suffixes are ignored.
//
// https://jdebp.uk/Softwares/nosh/guide/TerminalCapabilities.html
fn detect_from_term() -> Option<Self> {
env::var("TERM").ok().and_then(|v| {
if v.ends_with("-truecolor") || v.ends_with("-24bit") {
Some(Self::True)
} else if v.ends_with("-256color") || v.ends_with("-square") {
Some(Self::Ansi256)
} else {
None
}
})
}
#[cfg(not(windows))]
fn detect() -> Self {
use terminfo::capability::MaxColors;
use terminfo::Database;
if let Some(support) = Self::detect_from_colorterm().or_else(Self::detect_from_term) {
return support;
}
if let Ok(info) = Database::from_env() {
if let Some(MaxColors(colors)) = info.get() {
if colors < 256 {
return Self::Ansi16;
}
}
}
// Assume 256 colors by default (I'm not sure this is correct)
Self::Ansi256
}
#[cfg(windows)]
fn detect() -> Self {
use windows_version::OsVersion;
if let Some(support) = Self::detect_from_colorterm().or_else(Self::detect_from_term) {
return support;
}
// Windows 10.0.15063 or later supports 24-bit colors (true colors).
// https://github.com/Textualize/rich/issues/140
//
// Note that Windows 10.0.15063 is Windows 10 1703, which was released on April 5, 2017 so it is pretty old.
if OsVersion::current() >= OsVersion::new(10, 0, 0, 15063) {
Self::True
} else {
Self::Ansi16
}
}
}
pub struct PrinterOptions<'main> {
pub tab_width: usize,
pub theme: Option<&'main str>,
pub grid: bool,
pub background_color: bool,
pub color_support: TermColorSupport,
pub term_width: u16,
pub custom_assets: bool,
pub text_wrap: TextWrapMode,
pub first_only: bool,
pub ascii_lines: bool,
}
impl Default for PrinterOptions<'_> {
fn default() -> Self {
use terminal_size::{terminal_size, Width};
Self {
tab_width: 4,
theme: None,
grid: true,
background_color: false,
color_support: TermColorSupport::detect(),
custom_assets: false,
term_width: terminal_size().map(|(Width(w), _)| w).unwrap_or(80), // Note: `tput` returns 80 when tty is not found
text_wrap: TextWrapMode::Char,
first_only: false,
ascii_lines: false,
}
}
}
// Trait to replace printer implementation for unit tests
pub trait Printer {
fn print(&self, file: File) -> Result<()>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::EnvGuard;
#[test]
fn test_detect_true_color_from_env() {
struct Envs {
colorterm: Option<&'static str>,
term: Option<&'static str>,
want: TermColorSupport,
}
for test in [
Envs {
colorterm: Some("truecolor"),
term: None,
want: TermColorSupport::True,
},
Envs {
colorterm: Some("24bit"),
term: None,
want: TermColorSupport::True,
},
Envs {
colorterm: Some(""), // Setting some other values indicates 16 colors support
term: None,
want: TermColorSupport::Ansi16,
},
Envs {
colorterm: None,
term: Some("xterm-truecolor"),
want: TermColorSupport::True,
},
Envs {
colorterm: None,
term: Some("xterm-24bit"),
want: TermColorSupport::True,
},
Envs {
colorterm: None,
term: Some("xterm-256color"),
want: TermColorSupport::Ansi256,
},
Envs {
colorterm: None,
term: Some("xterm-square"),
want: TermColorSupport::Ansi256,
},
Envs {
colorterm: None,
term: Some("xterm-unknown"),
#[cfg(not(windows))]
want: TermColorSupport::Ansi256,
#[cfg(windows)]
want: TermColorSupport::True,
},
Envs {
colorterm: Some("truecolor"), // Checking COLORTERM is preceded
term: Some("xterm-256color"),
want: TermColorSupport::True,
},
Envs {
colorterm: Some(""),
term: Some("xterm-256color"),
want: TermColorSupport::Ansi16,
},
Envs {
colorterm: None,
term: None,
#[cfg(not(windows))]
want: TermColorSupport::Ansi256,
#[cfg(windows)]
want: TermColorSupport::True,
},
] {
let mut guard = EnvGuard::default();
let Envs {
colorterm,
term,
want,
} = test;
guard.set_env("COLORTERM", colorterm);
guard.set_env("TERM", term);
let detected = TermColorSupport::detect();
assert_eq!(detected, want, "COLORTERM={colorterm:?} and TERM={term:?}",);
}
}
}