arf-console 0.5.0

A cross-platform R console written in Rust
//! Startup banner formatting.

use crate::config::{Config, FormatterBackend, RSourceOverrideInfo, ReprexMode};
use crate::editor::prompt::get_r_version;

/// Format the startup banner.
///
/// All banner lines are prefixed with "# " so they appear as R comments,
/// ensuring copy-pasted output remains valid R code (especially useful for reprex mode).
///
/// The banner content varies based on configuration:
/// - Always shows version and edit mode info
/// - Shows reprex mode info when enabled
/// - Shows R initialization status
pub fn format_banner(
    config: &Config,
    r_initialized: bool,
    override_info: Option<&RSourceOverrideInfo>,
    formatter_backend: Option<FormatterBackend>,
) -> String {
    let mut lines = Vec::new();

    lines.push(format!("# arf console v{}", env!("CARGO_PKG_VERSION")));
    lines.push(format!("# Edit mode: {}", config.editor.mode));

    match config.startup.reprex {
        ReprexMode::Off => {}
        ReprexMode::On => lines.push(format!(
            "# Reprex: on | Comment: {:?}",
            config.reprex.comment
        )),
        ReprexMode::Format => {
            let formatter_label = match (config.reprex.formatter, formatter_backend) {
                (crate::config::ReprexFormatter::Auto, Some(backend)) => {
                    format!("auto ({})", backend.command())
                }
                (crate::config::ReprexFormatter::Auto, None) => "auto (unavailable)".to_string(),
                (selector, Some(backend)) => format!("{selector} ({})", backend.command()),
                (selector, None) => format!("{selector} (unavailable)"),
            };
            lines.push(format!(
                "# Reprex: format | Comment: {:?} | Formatter: {}",
                config.reprex.comment, formatter_label
            ));
        }
    }

    if r_initialized {
        let r_version = get_r_version();
        if r_version.is_empty() {
            lines.push("# R is ready.".to_string());
        } else {
            lines.push(format!("# R {} is ready.", r_version));
        }
        if let Some(info) = override_info {
            lines.push(format_override_line(info));
        }
    } else {
        lines.push("# R is not initialized. Commands will not be evaluated.".to_string());
    }

    lines.push("# Type :cmds for meta commands list, Ctrl+D to exit.".to_string());
    lines.push(String::new()); // Empty line at the end

    lines.join("\n")
}

/// Format the one-line override notice used when the startup banner is hidden.
pub(crate) fn format_override_line(info: &RSourceOverrideInfo) -> String {
    format!("# R source override: {}", info.display())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_banner_default_r_initialized() {
        let config = Config::default();
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_default_r_initialized", banner);
    }

    #[test]
    fn test_banner_default_r_not_initialized() {
        let config = Config::default();
        let banner = format_banner(&config, false, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_default_r_not_initialized", banner);
    }

    #[test]
    fn test_banner_reprex_mode() {
        let mut config = Config::default();
        config.startup.reprex = ReprexMode::On;
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_reprex_mode", banner);
    }

    #[test]
    fn test_banner_reprex_custom_comment() {
        let mut config = Config::default();
        config.startup.reprex = ReprexMode::On;
        config.reprex.comment = "## ".to_string();
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_reprex_custom_comment", banner);
    }

    #[test]
    fn test_banner_reprex_format_mode() {
        let mut config = Config::default();
        config.startup.reprex = ReprexMode::Format;
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_reprex_format_mode", banner);
    }

    #[test]
    fn test_banner_vi_mode() {
        let mut config = Config::default();
        config.editor.mode = crate::config::EditorMode::Vi;
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        insta::assert_snapshot!("banner_vi_mode", banner);
    }

    #[test]
    fn test_banner_all_lines_start_with_comment() {
        let config = Config::default();
        let banner = format_banner(&config, true, None, Some(FormatterBackend::Air));
        for line in banner.lines() {
            if !line.is_empty() {
                assert!(
                    line.starts_with("# "),
                    "Banner line should start with '# ': {:?}",
                    line
                );
            }
        }
    }

    #[test]
    fn test_banner_includes_r_source_override_line() {
        let config = Config::default();
        let info = RSourceOverrideInfo {
            provider: "toml-key".to_string(),
            file: Some("rproject.toml".into()),
            key: Some("project.r_version".to_string()),
            requested_version: "4.4".to_string(),
            resolved_version: "4.4.2".to_string(),
        };
        let banner = format_banner(&config, true, Some(&info), Some(FormatterBackend::Air));
        assert!(
            banner.contains(
                "# R source override: toml-key rproject.toml:project.r_version = \"4.4\""
            )
        );
    }

    #[test]
    fn test_hidden_banner_override_line_is_one_stderr_line() {
        let info = RSourceOverrideInfo {
            provider: "version-file".to_string(),
            file: Some(".r-version".into()),
            key: None,
            requested_version: "4.4".to_string(),
            resolved_version: "4.4.2".to_string(),
        };
        let line = format_override_line(&info);
        assert_eq!(
            line,
            "# R source override: version-file .r-version = \"4.4\""
        );
        assert!(!line.contains('\n'));
    }
}