use crate::terminal::capabilities::iterm2::ITerm2Protocol;
use crate::terminal::capabilities::*;
use std::fmt::{Display, Formatter};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TerminalProgram {
Dumb,
Ansi,
ITerm2,
Kitty,
WezTerm,
VSCode,
Ghostty,
Foot,
Xterm,
}
impl Display for TerminalProgram {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match *self {
TerminalProgram::Dumb => "dumb",
TerminalProgram::Ansi => "ansi",
TerminalProgram::ITerm2 => "iTerm2",
TerminalProgram::Kitty => "kitty",
TerminalProgram::WezTerm => "WezTerm",
TerminalProgram::VSCode => "vscode",
TerminalProgram::Ghostty => "ghostty",
TerminalProgram::Foot => "foot",
TerminalProgram::Xterm => "xterm",
};
write!(f, "{name}")
}
}
impl TerminalProgram {
fn detect_term() -> Option<Self> {
if let Ok(v) = std::env::var("XTERM_VERSION") {
if !v.is_empty() {
return Some(Self::Xterm);
}
}
match std::env::var("TERM").ok().as_deref() {
Some("wezterm") => Some(Self::WezTerm),
Some("xterm-kitty") => Some(Self::Kitty),
Some("xterm-ghostty") => Some(Self::Ghostty),
Some("foot") => Some(Self::Foot),
_ => None,
}
}
fn detect_term_program() -> Option<Self> {
match std::env::var("TERM_PROGRAM").ok().as_deref() {
Some("WezTerm") => Some(Self::WezTerm),
Some("iTerm.app") => Some(Self::ITerm2),
Some("ghostty") => Some(Self::Ghostty),
Some("vscode") => Some(Self::VSCode),
_ => None,
}
}
pub fn detect() -> Self {
Self::detect_term()
.or_else(Self::detect_term_program)
.unwrap_or(Self::Ansi)
}
pub fn capabilities(self) -> TerminalCapabilities {
let ansi = TerminalCapabilities {
style: Some(StyleCapability::Ansi),
image: None,
marks: None,
};
match self {
TerminalProgram::Dumb => TerminalCapabilities::default(),
TerminalProgram::Ansi => ansi,
TerminalProgram::ITerm2 => ansi
.with_mark_capability(MarkCapability::ITerm2(ITerm2Protocol))
.with_image_capability(ImageCapability::ITerm2(ITerm2Protocol)),
TerminalProgram::Kitty => ansi
.with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
TerminalProgram::WezTerm => ansi
.with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
TerminalProgram::VSCode => ansi,
TerminalProgram::Ghostty => ansi
.with_image_capability(ImageCapability::Kitty(self::kitty::KittyGraphicsProtocol)),
#[cfg(feature = "sixel")]
TerminalProgram::Foot => {
ansi.with_image_capability(ImageCapability::Sixel(self::sixel::SixelProtocol))
}
#[cfg(not(feature = "sixel"))]
TerminalProgram::Foot => ansi,
#[cfg(feature = "sixel")]
TerminalProgram::Xterm => {
ansi.with_image_capability(ImageCapability::Sixel(self::sixel::SixelProtocol))
}
#[cfg(not(feature = "sixel"))]
TerminalProgram::Xterm => ansi,
}
}
}
#[cfg(test)]
mod tests {
use crate::terminal::TerminalProgram;
use temp_env::with_vars;
#[test]
pub fn detect_term_kitty() {
with_vars(vec![("TERM", Some("xterm-kitty"))], || {
assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty)
})
}
#[test]
pub fn detect_term_wezterm() {
with_vars(vec![("TERM", Some("wezterm"))], || {
assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm)
})
}
#[test]
pub fn detect_term_program_wezterm() {
with_vars(
vec![
("TERM", Some("xterm-256color")),
("TERM_PROGRAM", Some("WezTerm")),
],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::WezTerm),
)
}
#[test]
pub fn detect_term_program_iterm2() {
with_vars(
vec![
("TERM", Some("xterm-256color")),
("TERM_PROGRAM", Some("iTerm.app")),
],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::ITerm2),
)
}
#[test]
pub fn detect_term_program_vscode() {
with_vars(
vec![
("TERM", Some("xterm-256color")),
("TERM_PROGRAM", Some("vscode")),
],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::VSCode),
)
}
#[test]
pub fn vscode_has_no_image_capability() {
assert!(TerminalProgram::VSCode.capabilities().image.is_none());
}
#[test]
pub fn detect_term_ghostty() {
with_vars(vec![("TERM", Some("xterm-ghostty"))], || {
assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty)
})
}
#[test]
pub fn detect_term_program_ghostty() {
with_vars(
vec![
("TERM", Some("xterm-256color")),
("TERM_PROGRAM", Some("ghostty")),
],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::Ghostty),
)
}
#[test]
pub fn detect_ansi() {
with_vars(
vec![("TERM", Some("xterm-256color")), ("TERM_PROGRAM", None)],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::Ansi),
)
}
#[test]
#[allow(non_snake_case)]
pub fn GH_230_detect_nested_kitty_from_iterm2() {
with_vars(
vec![
("TERM_PROGRAM", Some("iTerm.app")),
("TERM", Some("xterm-kitty")),
],
|| assert_eq!(TerminalProgram::detect(), TerminalProgram::Kitty),
)
}
#[test]
pub fn detect_term_foot() {
with_vars(vec![("TERM", Some("foot"))], || {
assert_eq!(TerminalProgram::detect(), TerminalProgram::Foot)
})
}
#[test]
pub fn detect_term_xterm() {
with_vars(vec![("XTERM_VERSION", Some("XTerm(410)"))], || {
assert_eq!(TerminalProgram::detect(), TerminalProgram::Xterm)
})
}
}