Lines
84.34 %
Functions
36.84 %
Branches
100 %
//! Chart renderer selector.
//!
//! Detects terminal graphics capability once at startup and picks the
//! highest-fidelity renderer the current terminal supports:
//! 1. [`Backend::Kitty`] — writes PNG via kitty's graphics APC. Works
//! in kitty, wezterm, ghostty, kitten-wrapped tmux, etc.
//! 2. [`Backend::Ratatui`] — braille / block-based charting inside
//! ratatui's canvas widget. Works everywhere ratatui does.
//! 3. [`Backend::Text`] — monospace ASCII block renderer, the fallback
//! used by the automation CLI.
use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Backend {
Kitty,
Ratatui,
Text,
}
/// Decide which chart backend to use given the current process
/// environment. Honours `NOMISYNC_TUI_CHART` for explicit overrides —
/// useful for CI recordings and for users whose terminal capability
/// the heuristics misidentify.
#[must_use]
pub fn detect_backend<F>(env_lookup: F) -> Backend
where
F: Fn(&str) -> Option<String>,
{
if let Some(forced) = env_lookup("NOMISYNC_TUI_CHART") {
match forced.to_ascii_lowercase().as_str() {
"kitty" => return Backend::Kitty,
"ratatui" => return Backend::Ratatui,
"text" => return Backend::Text,
_ => {}
if supports_kitty(&env_lookup) {
return Backend::Kitty;
Backend::Ratatui
fn supports_kitty<F>(env_lookup: &F) -> bool
if env_lookup("KITTY_WINDOW_ID").is_some() {
return true;
let term = env_lookup("TERM").unwrap_or_default();
if term.contains("kitty") {
let term_program = env_lookup("TERM_PROGRAM").unwrap_or_default();
matches!(term_program.as_str(), "WezTerm" | "ghostty")
/// Convenience wrapper that reads from the real process environment.
pub fn detect_from_env() -> Backend {
detect_backend(|name| env::var(name).ok())
/// Capability detection driven by the active [`Transport`]'s
/// `client_term`. The SSH transport returns the terminal type the
/// peer announced via `pty-req`; the local transport returns
/// `$TERM`. Falls back to the daemon's own environment for the
/// remaining hints (`KITTY_WINDOW_ID`, `TERM_PROGRAM`) — those have
/// no SSH equivalent today.
pub fn detect_for<T>(transport: &T) -> Backend
T: crate::transport::Transport,
let client_term = transport.client_term().map(str::to_string);
detect_backend(|name| match name {
"TERM" => client_term.clone(),
other => env::var(other).ok(),
})
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn env_from(pairs: &[(&str, &str)]) -> impl Fn(&str) -> Option<String> {
let map: HashMap<String, String> = pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect();
move |k: &str| map.get(k).cloned()
#[test]
fn plain_xterm_picks_ratatui() {
let env = env_from(&[("TERM", "xterm-256color")]);
assert_eq!(detect_backend(env), Backend::Ratatui);
fn kitty_window_id_picks_kitty() {
let env = env_from(&[("KITTY_WINDOW_ID", "1"), ("TERM", "xterm-256color")]);
assert_eq!(detect_backend(env), Backend::Kitty);
fn xterm_kitty_term_picks_kitty() {
let env = env_from(&[("TERM", "xterm-kitty")]);
fn wezterm_program_picks_kitty() {
let env = env_from(&[("TERM", "xterm-256color"), ("TERM_PROGRAM", "WezTerm")]);
fn ghostty_program_picks_kitty() {
let env = env_from(&[("TERM_PROGRAM", "ghostty")]);
fn empty_env_falls_back_to_ratatui() {
let env = env_from(&[]);
fn explicit_override_forces_text_backend() {
let env = env_from(&[("KITTY_WINDOW_ID", "1"), ("NOMISYNC_TUI_CHART", "text")]);
assert_eq!(detect_backend(env), Backend::Text);
fn explicit_override_forces_ratatui_backend() {
let env = env_from(&[("KITTY_WINDOW_ID", "1"), ("NOMISYNC_TUI_CHART", "ratatui")]);
fn explicit_override_forces_kitty_backend() {
let env = env_from(&[("TERM", "xterm"), ("NOMISYNC_TUI_CHART", "kitty")]);