textual 1.0.0-dev

A reactive TUI framework inspired by the Python Textual library
Documentation
use rich_rs::{Color, Style};
use std::fs::OpenOptions;
use std::io::Write;
use std::sync::OnceLock;

#[derive(Debug, Clone)]
pub struct DebugLayout {
    pub enabled: bool,
    pub show_sizes: bool,
    pub colors: Vec<u8>,
}

impl DebugLayout {
    pub fn disabled() -> Self {
        Self {
            enabled: false,
            show_sizes: false,
            colors: vec![196, 202, 208, 214, 220, 82, 45, 27, 129],
        }
    }

    pub fn enabled() -> Self {
        let mut layout = Self::disabled();
        layout.enabled = true;
        layout.show_sizes = true;
        layout
    }

    pub fn style_for(&self, index: usize) -> Style {
        let color = self.colors[index % self.colors.len()];
        Style::color(Color::from_ansi(color).into())
    }
}

impl Default for DebugLayout {
    fn default() -> Self {
        Self::disabled()
    }
}

pub(crate) fn debug_input(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_INPUT_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn debug_layout(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_LAYOUT_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn debug_style(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_STYLE_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn debug_render(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_RENDER_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn timing_enabled() -> bool {
    static ENABLED: OnceLock<bool> = OnceLock::new();
    *ENABLED.get_or_init(|| std::env::var("TEXTUAL_DEBUG_TIMING_FILE").is_ok())
}

pub(crate) fn debug_timing(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_TIMING_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn debug_message(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_MESSAGE_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn debug_border(line: &str) {
    static PATH: OnceLock<Option<String>> = OnceLock::new();
    let path = PATH.get_or_init(|| std::env::var("TEXTUAL_DEBUG_BORDER_FILE").ok());
    let Some(path) = path.as_ref() else {
        return;
    };
    if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{line}");
    }
}

pub(crate) fn border_debug_matches(label: &str) -> bool {
    if std::env::var("TEXTUAL_DEBUG_BORDER_FILE").is_err() {
        return false;
    }
    static FILTERS: OnceLock<Vec<String>> = OnceLock::new();
    let filters = FILTERS.get_or_init(|| {
        std::env::var("TEXTUAL_DEBUG_BORDER_FILTER")
            .ok()
            .map(|value| {
                value
                    .split(',')
                    .map(|part| part.trim().to_ascii_lowercase())
                    .filter(|part| !part.is_empty())
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default()
    });
    if filters.is_empty() {
        return true;
    }
    let label = label.to_ascii_lowercase();
    filters.iter().all(|filter| label.contains(filter))
}