pulldown_cmark_mdcat/terminal/
detect.rs1use crate::terminal::capabilities::iterm2::ITerm2Protocol;
10use crate::terminal::capabilities::*;
11use std::fmt::{Display, Formatter};
12
13#[derive(Debug, Copy, Clone, Eq, PartialEq)]
15pub enum TerminalProgram {
16 Dumb,
18 Ansi,
20 ITerm2,
27 Kitty,
34 WezTerm,
41 VSCode,
43 Ghostty,
47 Foot,
51 Xterm,
55}
56
57impl Display for TerminalProgram {
58 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59 let name = match *self {
60 TerminalProgram::Dumb => "dumb",
61 TerminalProgram::Ansi => "ansi",
62 TerminalProgram::ITerm2 => "iTerm2",
63 TerminalProgram::Kitty => "kitty",
64 TerminalProgram::WezTerm => "WezTerm",
65 TerminalProgram::VSCode => "vscode",
66 TerminalProgram::Ghostty => "ghostty",
67 TerminalProgram::Foot => "foot",
68 TerminalProgram::Xterm => "xterm",
69 };
70 write!(f, "{name}")
71 }
72}
73
74impl TerminalProgram {
75 fn detect_term() -> Option<Self> {
76 if let Ok(v) = std::env::var("XTERM_VERSION") {
77 if !v.is_empty() {
78 return Some(Self::Xterm);
79 }
80 }
81
82 match std::env::var("TERM").ok().as_deref() {
83 Some("wezterm") => Some(Self::WezTerm),
84 Some("xterm-kitty") => Some(Self::Kitty),
85 Some("xterm-ghostty") => Some(Self::Ghostty),
86 Some("foot") => Some(Self::Foot),
87 _ => None,
88 }
89 }
90
91 fn detect_term_program() -> Option<Self> {
92 match std::env::var("TERM_PROGRAM").ok().as_deref() {
93 Some("WezTerm") => Some(Self::WezTerm),
94 Some("iTerm.app") => Some(Self::ITerm2),
95 Some("ghostty") => Some(Self::Ghostty),
96 Some("vscode") => Some(Self::VSCode),
97 _ => None,
98 }
99 }
100
101 pub fn detect() -> Self {
127 Self::detect_term()
128 .or_else(Self::detect_term_program)
129 .unwrap_or(Self::Ansi)
130 }
131
132 pub fn capabilities(self) -> TerminalCapabilities {
134 let ansi = TerminalCapabilities {
135 style: Some(StyleCapability::Ansi),
136 image: None,
137 marks: None,
138 };
139 match self {
140 TerminalProgram::Dumb => TerminalCapabilities::default(),
141 TerminalProgram::Ansi => ansi,
142 TerminalProgram::ITerm2 => ansi
143 .with_mark_capability(MarkCapability::ITerm2(ITerm2Protocol))
144 .with_image_capability(ImageCapability::ITerm2(ITerm2Protocol)),
145 TerminalProgram::Kitty => ansi
146 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
147 TerminalProgram::WezTerm => ansi
148 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
149 TerminalProgram::VSCode => ansi,
150 TerminalProgram::Ghostty => ansi
151 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
152 #[cfg(feature = "sixel")]
153 TerminalProgram::Foot => {
154 ansi.with_image_capability(ImageCapability::Sixel(self::sixel::SixelProtocol))
155 }
156 #[cfg(not(feature = "sixel"))]
157 TerminalProgram::Foot => ansi,
158 #[cfg(feature = "sixel")]
159 TerminalProgram::Xterm => {
160 ansi.with_image_capability(ImageCapability::Sixel(self::sixel::SixelProtocol))
161 }
162 #[cfg(not(feature = "sixel"))]
163 TerminalProgram::Xterm => ansi,
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use crate::terminal::TerminalProgram;
171
172 use temp_env::with_vars;
173
174 #[test]
175 pub fn detect_term_kitty() {
176 with_vars(vec![("TERM", Some("xterm-kitty"))], || {
177 assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty)
178 })
179 }
180
181 #[test]
182 pub fn detect_term_wezterm() {
183 with_vars(vec![("TERM", Some("wezterm"))], || {
184 assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm)
185 })
186 }
187
188 #[test]
189 pub fn detect_term_program_wezterm() {
190 with_vars(
191 vec![
192 ("TERM", Some("xterm-256color")),
193 ("TERM_PROGRAM", Some("WezTerm")),
194 ],
195 || assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm),
196 )
197 }
198
199 #[test]
200 pub fn detect_term_program_iterm2() {
201 with_vars(
202 vec![
203 ("TERM", Some("xterm-256color")),
204 ("TERM_PROGRAM", Some("iTerm.app")),
205 ],
206 || assert_eq!(TerminalProgram::detect(), TerminalProgram::ITerm2),
207 )
208 }
209
210 #[test]
211 pub fn detect_term_program_vscode() {
212 with_vars(
213 vec![
214 ("TERM", Some("xterm-256color")),
215 ("TERM_PROGRAM", Some("vscode")),
216 ],
217 || assert_eq!(TerminalProgram::detect(), TerminalProgram::VSCode),
218 )
219 }
220
221 #[test]
222 pub fn vscode_has_no_image_capability() {
223 assert!(TerminalProgram::VSCode.capabilities().image.is_none());
224 }
225
226 #[test]
227 pub fn detect_term_ghostty() {
228 with_vars(vec![("TERM", Some("xterm-ghostty"))], || {
229 assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty)
230 })
231 }
232
233 #[test]
234 pub fn detect_term_program_ghostty() {
235 with_vars(
236 vec![
237 ("TERM", Some("xterm-256color")),
238 ("TERM_PROGRAM", Some("ghostty")),
239 ],
240 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty),
241 )
242 }
243
244 #[test]
245 pub fn detect_ansi() {
246 with_vars(
247 vec![("TERM", Some("xterm-256color")), ("TERM_PROGRAM", None)],
248 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ansi),
249 )
250 }
251
252 #[test]
254 #[allow(non_snake_case)]
255 pub fn GH_230_detect_nested_kitty_from_iterm2() {
256 with_vars(
257 vec![
258 ("TERM_PROGRAM", Some("iTerm.app")),
259 ("TERM", Some("xterm-kitty")),
260 ],
261 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty),
262 )
263 }
264
265 #[test]
266 pub fn detect_term_foot() {
267 with_vars(vec![("TERM", Some("foot"))], || {
268 assert_eq!(TerminalProgram::detect(), TerminalProgram::Foot)
269 })
270 }
271
272 #[test]
273 pub fn detect_term_xterm() {
274 with_vars(vec![("XTERM_VERSION", Some("XTerm(410)"))], || {
275 assert_eq!(TerminalProgram::detect(), TerminalProgram::Xterm)
276 })
277 }
278}