elementary-row-operation-verifier 0.0.1

A tool to verify the correctness of elementary row operations on matrices
Documentation
//! TUI 内容构建:矩阵显示 + 着色

use ratatui::{
    style::{Color, Modifier, Style},
    text::{Line, Span},
};

use crate::matrix::Matrix;
use crate::parser::Step;
use crate::verifier::VerificationResult;

pub fn build_content(
    ori: &Matrix,
    steps: &[Step],
    results: &[VerificationResult],
    show_detail: bool,
    col_widths: &[usize],
) -> Vec<Line<'static>> {
    let mut lines: Vec<Line> = Vec::new();

    lines.push(Line::from(Span::styled(
        "── ori ──",
        Style::default()
            .fg(Color::Yellow)
            .add_modifier(Modifier::BOLD),
    )));
    for r in 0..ori.nrows() {
        lines.push(fmt_row(ori, r, &Style::default(), col_widths));
    }
    lines.push(Line::raw(""));

    for (step_idx, step) in steps.iter().enumerate() {
        let result = results.get(step_idx);
        let (_, color) = match result {
            Some(VerificationResult::Correct { .. }) => ("", Color::Green),
            Some(VerificationResult::Incorrect { .. }) => ("", Color::Red),
            Some(VerificationResult::Error { .. }) => ("", Color::Yellow),
            None => ("?", Color::Gray),
        };
        lines.push(Line::from(Span::styled(
            format!("step {}", step_idx + 1),
            Style::default().fg(color).add_modifier(Modifier::BOLD),
        )));
        lines.push(Line::raw(""));

        let is_col = step.kind == crate::parser::OpKind::Col;

        let ref_indices: Vec<usize> = step
            .ops
            .iter()
            .filter_map(|(_, s)| {
                crate::math::parse_operation(s, 0).ok().and_then(|op| {
                    if is_col {
                        op.source_col()
                    } else {
                        op.source_row()
                    }
                })
            })
            .collect();

        for r in 0..step.expected.nrows() {
            let op = if !is_col {
                step.ops.iter().find(|(row, _)| *row == r + 1)
            } else {
                None
            };
            let is_read = !op.is_some() && !is_col && ref_indices.contains(&(r + 1));

            let (row_style, op_text, op_style) = match op {
                Some((_, op_str)) => {
                    let s = match result {
                        Some(VerificationResult::Correct { .. }) => {
                            Style::default().fg(Color::Green)
                        }
                        Some(VerificationResult::Incorrect { .. }) => {
                            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
                        }
                        _ => Style::default(),
                    };
                    (Style::default(), format!("  {}", op_str), s)
                }
                None if is_read => (
                    Style::default().fg(Color::Blue),
                    String::new(),
                    Style::default().fg(Color::Blue),
                ),
                None => (Style::default(), String::new(), Style::default()),
            };

            let mut spans: Vec<Span> = Vec::new();
            for c in 0..step.expected.ncols() {
                let ev = step.expected[(r, c)];
                let wrong = match result {
                    Some(VerificationResult::Incorrect { got, .. }) => {
                        (ev - got[(r, c)]).abs() > 1e-10
                    }
                    _ => false,
                };
                // 列变换:被引用列标蓝
                let col_style = if is_col && ref_indices.contains(&(c + 1)) {
                    Style::default().fg(Color::Blue)
                } else {
                    row_style
                };
                let cs = if wrong {
                    Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
                } else {
                    col_style
                };
                let w = col_widths.get(c).copied().unwrap_or(6);
                spans.push(Span::styled(format!("{:<w$}", fmt_num(ev), w = w), cs));
            }
            spans.push(Span::styled(op_text, op_style));
            lines.push(Line::from(spans));
        }

        // 列变换:显示原始 | 链操作行
        if is_col {
            for op_row in &step.op_slices {
                let mut spans: Vec<Span> = Vec::new();
                for (c, cell) in op_row.iter().enumerate() {
                    let is_target = step.ops.iter().any(|(col, _)| *col == c + 1);
                    let is_ref = !is_target && ref_indices.contains(&(c + 1));
                    let style = if cell == "|" {
                        Style::default().fg(Color::DarkGray)
                    } else if is_target {
                        match result {
                            Some(VerificationResult::Correct { .. }) => {
                                Style::default().fg(Color::Green)
                            }
                            Some(VerificationResult::Incorrect { .. }) => {
                                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
                            }
                            _ => Style::default(),
                        }
                    } else if is_ref {
                        Style::default().fg(Color::Blue)
                    } else {
                        Style::default()
                    };
                    let display = if cell == "." { "" } else { cell.as_str() };
                    let w = col_widths.get(c).copied().unwrap_or(6);
                    let txt = format!("{:<w$}", display, w = w);
                    spans.push(Span::styled(txt, style));
                }
                lines.push(Line::from(spans));
            }
        }
        lines.push(Line::raw(""));

        if show_detail {
            if let Some(VerificationResult::Error { message, .. }) = result {
                lines.push(Line::from(Span::styled(
                    format!("  Error: {}", message),
                    Style::default().fg(Color::Yellow),
                )));
                lines.push(Line::raw(""));
            }
        }
    }
    lines
}

pub fn fmt_row(m: &Matrix, r: usize, style: &Style, widths: &[usize]) -> Line<'static> {
    let spans: Vec<Span> = (0..m.ncols())
        .map(|c| {
            let w = widths.get(c).copied().unwrap_or(6);
            Span::styled(format!("{:<w$}", fmt_num(m[(r, c)]), w = w), *style)
        })
        .collect();
    Line::from(spans)
}

pub fn fmt_num(v: f64) -> String {
    if (v - v.round()).abs() < 1e-10 {
        format!("{:.0}", v)
    } else {
        format!("{:.2}", v)
    }
}