elio 1.1.0

Snappy, batteries-included terminal file manager with rich previews, inline images, bulk actions, and trash support.
Documentation
use super::{LINE_LIMIT, StructuredPreview, styled};
use crate::{file_info::StructuredFormat, preview::appearance as theme};
use ratatui::{
    style::Modifier,
    text::{Line, Span},
};

pub(super) fn render_dotenv_preview(text: &str) -> StructuredPreview {
    let palette = theme::code_preview_palette();
    let entries = parse_dotenv_entries(text);
    let key_width = entries
        .iter()
        .filter_map(|entry| match entry {
            DotenvEntry::Binding { key, .. } => Some(key.len()),
            _ => None,
        })
        .max()
        .unwrap_or(0)
        .min(32);

    let mut lines = Vec::new();
    let mut truncated = false;
    for entry in entries {
        if lines.len() >= LINE_LIMIT {
            truncated = true;
            break;
        }

        let line = match entry {
            DotenvEntry::Blank => Line::from(""),
            DotenvEntry::Comment(comment) => {
                Line::from(vec![styled(&comment, palette.comment, Modifier::ITALIC)])
            }
            DotenvEntry::Binding { key, value } => Line::from(vec![
                styled(
                    &format!("{key:width$}", width = key_width),
                    palette.function,
                    Modifier::BOLD,
                ),
                styled(" = ", palette.operator, Modifier::empty()),
                styled(&value, palette.string, Modifier::empty()),
            ]),
            DotenvEntry::Raw(raw) => Line::from(vec![Span::raw(raw)]),
        };
        lines.push(line);
    }

    StructuredPreview {
        lines: if lines.is_empty() {
            vec![Line::from("File is empty")]
        } else {
            lines
        },
        detail: StructuredFormat::Dotenv.detail_label(),
        truncation_note: truncated.then(|| format!("showing first {LINE_LIMIT} lines")),
    }
}

#[derive(Clone, Debug)]
enum DotenvEntry {
    Blank,
    Comment(String),
    Binding { key: String, value: String },
    Raw(String),
}

fn parse_dotenv_entries(text: &str) -> Vec<DotenvEntry> {
    text.lines()
        .map(|line| {
            let trimmed = line.trim();
            if trimmed.is_empty() {
                return DotenvEntry::Blank;
            }
            if trimmed.starts_with('#') {
                return DotenvEntry::Comment(trimmed.to_string());
            }

            let body = trimmed.strip_prefix("export ").unwrap_or(trimmed);
            if let Some((key, value)) = body.split_once('=') {
                return DotenvEntry::Binding {
                    key: key.trim().to_string(),
                    value: value.trim().to_string(),
                };
            }

            DotenvEntry::Raw(line.to_string())
        })
        .collect()
}