rmux 0.9.0

A local terminal multiplexer with a tmux-style CLI, daemon runtime, Rust SDK, and ratatui integration.
use qrcode::{Color as QrColor, EcLevel, QrCode};
use ratatui::{
    style::{Color, Style},
    text::{Line, Span},
};

const QUIET_ZONE: usize = 4;
const QR_LIGHT: Color = Color::Indexed(15);

#[derive(Clone, Copy)]
pub(super) enum RenderMode {
    Compact,
    Plain,
    TerminalSafe,
}

pub(super) fn render_lines(
    data: &str,
    mode: RenderMode,
) -> Result<Vec<Line<'static>>, qrcode::types::QrError> {
    let rows = qr_rows(data)?;
    Ok(match mode {
        RenderMode::Compact => half_block_lines(&rows),
        RenderMode::Plain => plain_lines(&rows),
        RenderMode::TerminalSafe => terminal_safe_lines(&rows),
    })
}

pub(super) fn width(data: &str, mode: RenderMode) -> usize {
    qr_rows(data).map_or(0, |rows| {
        let modules = rows.first().map_or(0, Vec::len);
        match mode {
            RenderMode::Compact => modules,
            RenderMode::Plain | RenderMode::TerminalSafe => modules.saturating_mul(2),
        }
    })
}

pub(super) fn height(data: &str, mode: RenderMode) -> usize {
    qr_rows(data).map_or(0, |rows| match mode {
        RenderMode::Compact => rows.len().div_ceil(2),
        RenderMode::Plain | RenderMode::TerminalSafe => rows.len(),
    })
}

fn qr_rows(data: &str) -> Result<Vec<Vec<bool>>, qrcode::types::QrError> {
    let code = QrCode::with_error_correction_level(data.as_bytes(), EcLevel::L)?;
    let size = code.width() + QUIET_ZONE * 2;
    let mut rows = Vec::new();

    for y in 0..size {
        let mut row = Vec::with_capacity(size);
        for x in 0..size {
            row.push(qr_dark(&code, x, y));
        }
        rows.push(row);
    }

    Ok(rows)
}

fn half_block_lines(rows: &[Vec<bool>]) -> Vec<Line<'static>> {
    rows.chunks(2)
        .map(|pair| {
            let top = pair[0].as_slice();
            let bottom = pair.get(1).map(Vec::as_slice);
            let mut line = String::new();
            for (x, top_dark) in top.iter().copied().enumerate() {
                let bottom_dark = bottom.and_then(|row| row.get(x)).copied().unwrap_or(false);
                line.push(match (top_dark, bottom_dark) {
                    (false, false) => ' ',
                    (true, false) => '',
                    (false, true) => '',
                    (true, true) => '',
                });
            }
            Line::from(Span::styled(
                line,
                Style::default().fg(Color::Black).bg(QR_LIGHT),
            ))
        })
        .collect()
}

fn terminal_safe_lines(rows: &[Vec<bool>]) -> Vec<Line<'static>> {
    rows.iter().map(|row| full_cell_line(row)).collect()
}

fn plain_lines(rows: &[Vec<bool>]) -> Vec<Line<'static>> {
    rows.iter()
        .map(|row| {
            let mut line = String::new();
            for dark in row {
                if *dark {
                    line.push_str("██");
                } else {
                    line.push_str("  ");
                }
            }
            Line::from(line)
        })
        .collect()
}

fn full_cell_line(row: &[bool]) -> Line<'static> {
    let mut spans = Vec::new();
    let mut index = 0;
    while index < row.len() {
        let dark = row[index];
        let start = index;
        while index < row.len() && row[index] == dark {
            index += 1;
        }
        let cells = index.saturating_sub(start).saturating_mul(2);
        spans.push(Span::styled(
            " ".repeat(cells),
            Style::default().bg(if dark { Color::Black } else { QR_LIGHT }),
        ));
    }
    Line::from(spans)
}

fn qr_dark(code: &QrCode, x: usize, y: usize) -> bool {
    if x < QUIET_ZONE || y < QUIET_ZONE {
        return false;
    }
    let qx = x - QUIET_ZONE;
    let qy = y - QUIET_ZONE;
    qx < code.width() && qy < code.width() && code[(qx, qy)] == QrColor::Dark
}