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 Terminology,
31 Kitty,
38 WezTerm,
45 VSCode,
47 Ghostty,
51}
52
53impl Display for TerminalProgram {
54 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
55 let name = match *self {
56 TerminalProgram::Dumb => "dumb",
57 TerminalProgram::Ansi => "ansi",
58 TerminalProgram::ITerm2 => "iTerm2",
59 TerminalProgram::Terminology => "Terminology",
60 TerminalProgram::Kitty => "kitty",
61 TerminalProgram::WezTerm => "WezTerm",
62 TerminalProgram::VSCode => "vscode",
63 TerminalProgram::Ghostty => "ghostty",
64 };
65 write!(f, "{name}")
66 }
67}
68
69impl TerminalProgram {
70 fn detect_term() -> Option<Self> {
71 match std::env::var("TERM").ok().as_deref() {
72 Some("wezterm") => Some(Self::WezTerm),
73 Some("xterm-kitty") => Some(Self::Kitty),
74 Some("xterm-ghostty") => Some(Self::Ghostty),
75 _ => None,
76 }
77 }
78
79 fn detect_term_program() -> Option<Self> {
80 match std::env::var("TERM_PROGRAM").ok().as_deref() {
81 Some("WezTerm") => Some(Self::WezTerm),
82 Some("iTerm.app") => Some(Self::ITerm2),
83 Some("ghostty") => Some(Self::Ghostty),
84 Some("vscode") => Some(Self::VSCode),
85 _ => None,
86 }
87 }
88
89 pub fn detect() -> Self {
115 Self::detect_term()
116 .or_else(Self::detect_term_program)
117 .unwrap_or(Self::Ansi)
118 }
119
120 pub fn capabilities(self) -> TerminalCapabilities {
122 let ansi = TerminalCapabilities {
123 style: Some(StyleCapability::Ansi),
124 image: None,
125 marks: None,
126 };
127 match self {
128 TerminalProgram::Dumb => TerminalCapabilities::default(),
129 TerminalProgram::Ansi => ansi,
130 TerminalProgram::ITerm2 => ansi
131 .with_mark_capability(MarkCapability::ITerm2(ITerm2Protocol))
132 .with_image_capability(ImageCapability::ITerm2(ITerm2Protocol)),
133 TerminalProgram::Terminology => ansi,
134 TerminalProgram::Kitty => ansi
135 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
136 TerminalProgram::WezTerm => ansi
137 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
138 TerminalProgram::VSCode => ansi,
139 TerminalProgram::Ghostty => ansi
140 .with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use crate::terminal::TerminalProgram;
148
149 use temp_env::with_vars;
150
151 #[test]
152 pub fn detect_term_kitty() {
153 with_vars(vec![("TERM", Some("xterm-kitty"))], || {
154 assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty)
155 })
156 }
157
158 #[test]
159 pub fn detect_term_wezterm() {
160 with_vars(vec![("TERM", Some("wezterm"))], || {
161 assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm)
162 })
163 }
164
165 #[test]
166 pub fn detect_term_program_wezterm() {
167 with_vars(
168 vec![
169 ("TERM", Some("xterm-256color")),
170 ("TERM_PROGRAM", Some("WezTerm")),
171 ],
172 || assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm),
173 )
174 }
175
176 #[test]
177 pub fn detect_term_program_iterm2() {
178 with_vars(
179 vec![
180 ("TERM", Some("xterm-256color")),
181 ("TERM_PROGRAM", Some("iTerm.app")),
182 ],
183 || assert_eq!(TerminalProgram::detect(), TerminalProgram::ITerm2),
184 )
185 }
186
187 #[test]
188 pub fn does_not_detect_terminology() {
189 with_vars(
190 vec![
191 ("TERM", Some("xterm-256color")),
192 ("TERM_PROGRAM", None),
193 ("TERMINOLOGY", Some("1")),
194 ],
195 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ansi),
196 );
197 with_vars(
198 vec![
199 ("TERM", Some("xterm-256color")),
200 ("TERM_PROGRAM", None),
201 ("TERMINOLOGY", Some("0")),
202 ],
203 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ansi),
204 );
205 }
206
207 #[test]
208 pub fn detect_term_program_vscode() {
209 with_vars(
210 vec![
211 ("TERM", Some("xterm-256color")),
212 ("TERM_PROGRAM", Some("vscode")),
213 ],
214 || assert_eq!(TerminalProgram::detect(), TerminalProgram::VSCode),
215 )
216 }
217
218 #[test]
219 pub fn vscode_and_terminology_have_no_image_capability() {
220 assert!(TerminalProgram::VSCode.capabilities().image.is_none());
221 assert!(TerminalProgram::Terminology.capabilities().image.is_none());
222 }
223
224 #[test]
225 pub fn detect_term_ghostty() {
226 with_vars(vec![("TERM", Some("xterm-ghostty"))], || {
227 assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty)
228 })
229 }
230
231 #[test]
232 pub fn detect_term_program_ghostty() {
233 with_vars(
234 vec![
235 ("TERM", Some("xterm-256color")),
236 ("TERM_PROGRAM", Some("ghostty")),
237 ],
238 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty),
239 )
240 }
241
242 #[test]
243 pub fn detect_ansi() {
244 with_vars(
245 vec![
246 ("TERM", Some("xterm-256color")),
247 ("TERM_PROGRAM", None),
248 ("TERMINOLOGY", None),
249 ],
250 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Ansi),
251 )
252 }
253
254 #[test]
256 #[allow(non_snake_case)]
257 pub fn GH_230_detect_nested_kitty_from_iterm2() {
258 with_vars(
259 vec![
260 ("TERM_PROGRAM", Some("iTerm.app")),
261 ("TERM", Some("xterm-kitty")),
262 ],
263 || assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty),
264 )
265 }
266}