use std::sync::OnceLock;
use regex::Regex;
use crate::diagnostic::{Diagnostic, Fix};
use crate::regex_util::compile_static;
use crate::rule::LintRule;
use mdwright_document::Document;
use mdwright_latex::latex_symbol;
pub struct LatexCommand;
fn pattern() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| compile_static(r"\\\\?([A-Za-z]+)(?:\{[^}]*\})?"))
}
impl LintRule for LatexCommand {
fn name(&self) -> &str {
"latex-command"
}
fn description(&self) -> &str {
"LaTeX control sequence in prose (opt-in for Unicode-math projects)."
}
fn explain(&self) -> &str {
include_str!("explain/latex_command.md")
}
fn produces_fix(&self) -> bool {
true
}
fn is_default(&self) -> bool {
false
}
fn check(&self, doc: &Document, out: &mut Vec<Diagnostic>) {
let math = doc.math_regions();
for chunk in doc.prose_chunks() {
for cap in pattern().captures_iter(&chunk.text) {
let Some(m) = cap.get(0) else { continue };
let Some(name_match) = cap.get(1) else {
continue;
};
let abs_start = chunk.byte_offset.saturating_add(m.start());
let abs_end = chunk.byte_offset.saturating_add(m.end());
if math
.iter()
.any(|r| r.range.start <= abs_start && abs_end <= r.range.end)
{
continue;
}
let name = name_match.as_str();
let fix = latex_symbol(name).map(|u| Fix {
replacement: u.to_owned(),
safe: true,
});
let message = format!(
"LaTeX command `\\{name}` in prose — replace with Unicode math; \
this project does not render LaTeX"
);
if let Some(d) = Diagnostic::at(doc, chunk.byte_offset, m.range(), message, fix) {
out.push(d);
}
}
}
}
}