Skip to main content

anstyle_progress/
query.rs

1/// Determines whether the terminal supports ANSI OSC 9;4.
2pub fn supports_term_progress(is_terminal: bool) -> bool {
3    if !is_terminal {
4        return false;
5    }
6
7    // Terminal feature detection, includes
8    // - iTerm support in v3.6.6
9    let term_features = std::env::var("TERM_FEATURES")
10        .ok()
11        .map(|v| term_features_has_progress(&v))
12        .unwrap_or(false);
13    if term_features {
14        return true;
15    }
16
17    // https://github.com/wezterm/wezterm/issues/6581
18    // https://ghostty.org/docs/install/release-notes/1-2-0#graphical-progress-bars
19    let term_program = std::env::var("TERM_PROGRAM").ok();
20    if matches!(term_program.as_deref(), Some("WezTerm") | Some("ghostty")) {
21        return true;
22    }
23
24    // https://github.com/microsoft/terminal/pull/8055
25    if std::env::var("WT_SESSION").is_ok() {
26        return true;
27    }
28
29    // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
30    if std::env::var("ConEmuANSI").ok() == Some("ON".into()) {
31        return true;
32    }
33
34    // Ptyxis added OSC 9;4 support in 48.0.
35    // See https://gitlab.gnome.org/chergert/ptyxis/-/issues/305
36    let ptyxis = std::env::var("PTYXIS_VERSION")
37        .ok()
38        .and_then(|version| version.split(".").next()?.parse::<i32>().ok())
39        .map(|major_version| major_version >= 48)
40        .unwrap_or(false);
41    if ptyxis {
42        return true;
43    }
44
45    // Konsole added support in 26.04.0.
46    // https://invent.kde.org/utilities/konsole/-/merge_requests/1054
47    let konsole = std::env::var("KONSOLE_VERSION")
48        .ok()
49        .and_then(|version| version.parse::<u32>().ok())
50        .map(|version| version >= 260400)
51        .unwrap_or(false);
52    if konsole {
53        return true;
54    }
55
56    false
57}
58
59// For iTerm, the TERM_FEATURES value "P" indicates OSC 9;4 support.
60//
61// Context: https://iterm2.com/feature-reporting/
62fn term_features_has_progress(value: &str) -> bool {
63    let mut current = String::new();
64
65    for ch in value.chars() {
66        if !ch.is_ascii_alphanumeric() {
67            break;
68        }
69        if ch.is_ascii_uppercase() {
70            if current == "P" {
71                return true;
72            }
73            current.clear();
74            current.push(ch);
75        } else {
76            current.push(ch);
77        }
78    }
79    current == "P"
80}
81
82#[cfg(test)]
83mod tests {
84    use super::term_features_has_progress;
85
86    #[test]
87    fn term_features_progress_detection() {
88        // With PROGRESS feature ("P")
89        assert!(term_features_has_progress("MBT2ScP"));
90
91        // Without PROGRESS feature
92        assert!(!term_features_has_progress("MBT2Sc"));
93    }
94}