elio 1.5.0

Snappy, batteries-included terminal file manager with rich previews, inline images, bulk actions, and trash support.
Documentation
mod kv;
mod logs;
mod toml;
mod tree;

use crate::file_info::StructuredFormat;
use ratatui::{
    style::{Modifier, Style},
    text::{Line, Span},
};

pub(super) struct StructuredPreviewAttempt {
    pub preview: Option<StructuredPreview>,
    pub note: Option<String>,
}

pub(super) struct StructuredPreview {
    pub lines: Vec<Line<'static>>,
    pub detail: &'static str,
    pub truncation_note: Option<String>,
}

pub(super) const LINE_LIMIT: usize = super::PREVIEW_RENDER_LINE_LIMIT;

pub(super) fn render_structured_preview(
    text: &str,
    format: StructuredFormat,
    input_truncated: bool,
) -> StructuredPreviewAttempt {
    if input_truncated {
        return StructuredPreviewAttempt {
            preview: None,
            note: Some("formatted preview unavailable for partial file".to_string()),
        };
    }

    let preview = match format {
        StructuredFormat::Json => tree::render_json_preview(text, format.detail_label()),
        StructuredFormat::Jsonc | StructuredFormat::Json5 => {
            tree::render_json5_preview(text, format.detail_label())
        }
        StructuredFormat::Toml => toml::render_toml_preview(text, format.detail_label()),
        StructuredFormat::Yaml => tree::render_yaml_preview(text, format.detail_label()),
        StructuredFormat::Dotenv => Some(kv::render_dotenv_preview(text)),
        StructuredFormat::Log => logs::render_log_preview(text),
    };

    StructuredPreviewAttempt {
        preview,
        note: None,
    }
}

pub(super) fn styled(
    text: &str,
    color: ratatui::style::Color,
    modifier: Modifier,
) -> Span<'static> {
    Span::styled(
        text.to_string(),
        Style::default().fg(color).add_modifier(modifier),
    )
}

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

    #[test]
    fn jsonc_structured_preview_accepts_comments_and_trailing_commas() {
        let attempt = render_structured_preview(
            "{\n  // comment\n  \"name\": \"elio\",\n}\n",
            StructuredFormat::Jsonc,
            false,
        );

        let preview = attempt.preview.expect("jsonc should render");
        assert_eq!(preview.detail, "JSONC");
        assert!(
            preview
                .lines
                .iter()
                .flat_map(|line| line.spans.iter())
                .any(|span| span.content.contains("name"))
        );
    }

    #[test]
    fn dotenv_structured_preview_aligns_bindings() {
        let attempt =
            render_structured_preview("APP_ENV=dev\nPORT=3000\n", StructuredFormat::Dotenv, false);

        let preview = attempt.preview.expect("dotenv should render");
        assert_eq!(preview.detail, ".env");
        assert!(
            preview
                .lines
                .iter()
                .flat_map(|line| line.spans.iter())
                .any(|span| span.content.contains("APP_ENV"))
        );
    }

    #[test]
    fn toml_structured_preview_keeps_toml_shape() {
        let attempt = render_structured_preview(
            "[package]\nname = \"elio\"\nversion = \"0.1.0\"\n\n[dependencies]\nanyhow = \"1.0\"\nimage = { version = \"0.25\", default-features = false }\n\n[profile.release]\nstrip = true\n",
            StructuredFormat::Toml,
            false,
        );

        let preview = attempt.preview.expect("toml should render");
        assert_eq!(preview.detail, "TOML");

        let lines = preview
            .lines
            .iter()
            .map(|line| {
                line.spans
                    .iter()
                    .map(|span| span.content.as_ref())
                    .collect::<String>()
            })
            .collect::<Vec<_>>();

        let package_index = lines
            .iter()
            .position(|line| line.contains("[package]"))
            .expect("package header should be present");
        let dependencies_index = lines
            .iter()
            .position(|line| line.contains("[dependencies]"))
            .expect("dependencies header should be present");
        let profile_index = lines
            .iter()
            .position(|line| line.contains("[profile.release]"))
            .expect("profile header should be present");

        assert!(package_index < dependencies_index);
        assert!(dependencies_index < profile_index);
        assert!(lines.iter().any(|line| line.contains("name = \"elio\"")));
        assert!(lines.iter().any(|line| line.contains("anyhow = \"1.0\"")));
        assert!(
            lines
                .iter()
                .any(|line| line.contains("image = { version = \"0.25\""))
        );
        assert!(!lines.iter().any(|line| line.contains("root: object")));
    }

    #[test]
    fn log_structured_preview_includes_level_summary() {
        let attempt = render_structured_preview(
            "2026-03-10T12:00:00Z ERROR request_id=42 path=/login failed\n",
            StructuredFormat::Log,
            false,
        );

        let preview = attempt.preview.expect("log should render");
        assert_eq!(preview.detail, "Log");
        assert!(
            preview
                .lines
                .iter()
                .flat_map(|line| line.spans.iter())
                .any(|span| span.content.contains("ERROR"))
        );
        assert!(
            preview
                .lines
                .iter()
                .flat_map(|line| line.spans.iter())
                .any(|span| span.content.contains("request_id"))
        );
    }
}