use std::sync::LazyLock;
use regex::Regex;
use crate::models::highlight::{CodeBlock, CodeToken, add_classes};
use crate::models::options::{
Classes, ConfiguredTransformer, HighlightTransformer, TransformerName,
};
use crate::utils::notation::apply::{Notation, apply_notation};
use crate::utils::notation::parse::{Comment, parse_comments};
static META_LINES: LazyLock<Option<Regex>> =
LazyLock::new(|| Regex::new(r"\{([\d,-]+)\}").ok());
pub(crate) fn transform_code(
block: &mut CodeBlock,
transformers: &[HighlightTransformer],
) {
let transformers: Vec<ConfiguredTransformer> =
transformers.iter().map(configure_transformer).collect();
let highlighted = read_meta_lines(&block.meta);
for (index, line) in block.lines.iter_mut().enumerate() {
let number = index + 1;
for transformer in &transformers {
match transformer {
ConfiguredTransformer::MetaHighlight {
class_name,
zero_indexed,
} => {
if block.meta.is_empty() {
continue;
}
let effective = if *zero_indexed { number - 1 } else { number };
let effective = effective as i64;
if highlighted
.iter()
.any(|&(start, end)| start <= effective && effective <= end)
{
let classes = read_classes(class_name, "highlighted");
add_classes(&mut line.classes, &classes);
}
}
ConfiguredTransformer::RenderLineNumber {
class_line_number,
start,
} => {
let token = CodeToken {
text: (start.unwrap_or(1) + number - 1).to_string(),
style: String::new(),
classes: read_classes(class_line_number, "line-number"),
};
line.tokens.insert(0, Some(token));
}
_ => {}
}
}
}
let jsx = matches!(block.language.as_str(), "jsx" | "tsx");
let mut comments: Option<Vec<Comment>> = None;
for transformer in &transformers {
let Some(notation) = read_notation(transformer) else {
continue;
};
let comments = comments.get_or_insert_with(|| parse_comments(block, jsx));
apply_notation(block, comments, ¬ation);
}
}
fn configure_transformer(transformer: &HighlightTransformer) -> ConfiguredTransformer {
let name = match transformer {
HighlightTransformer::Configured(configured) => return configured.clone(),
HighlightTransformer::Name(name) => name,
};
match name {
TransformerName::NotationDiff => ConfiguredTransformer::NotationDiff {
class_line_add: None,
class_line_remove: None,
class_active_pre: None,
class_active_code: None,
},
TransformerName::NotationFocus => ConfiguredTransformer::NotationFocus {
class_active_line: None,
class_active_pre: None,
class_active_code: None,
},
TransformerName::NotationHighlight => ConfiguredTransformer::NotationHighlight {
class_active_line: None,
class_active_pre: None,
class_active_code: None,
},
TransformerName::NotationErrorLevel => {
ConfiguredTransformer::NotationErrorLevel {
class_map: None,
class_active_pre: None,
class_active_code: None,
}
}
TransformerName::MetaHighlight => ConfiguredTransformer::MetaHighlight {
class_name: None,
zero_indexed: false,
},
TransformerName::RenderLineNumber => ConfiguredTransformer::RenderLineNumber {
class_line_number: None,
start: None,
},
}
}
fn read_notation(transformer: &ConfiguredTransformer) -> Option<Notation> {
let classes = read_classes;
Some(match transformer {
ConfiguredTransformer::NotationDiff {
class_line_add,
class_line_remove,
class_active_pre,
class_active_code,
} => Notation {
class_map: vec![
("++".into(), classes(class_line_add, "diff add")),
("--".into(), classes(class_line_remove, "diff remove")),
],
active_pre: classes(class_active_pre, "has-diff"),
active_code: classes(class_active_code, ""),
},
ConfiguredTransformer::NotationFocus {
class_active_line,
class_active_pre,
class_active_code,
} => Notation {
class_map: vec![("focus".into(), classes(class_active_line, "focused"))],
active_pre: classes(class_active_pre, "has-focused"),
active_code: classes(class_active_code, ""),
},
ConfiguredTransformer::NotationHighlight {
class_active_line,
class_active_pre,
class_active_code,
} => {
let line = classes(class_active_line, "highlighted");
Notation {
class_map: vec![("highlight".into(), line.clone()), ("hl".into(), line)],
active_pre: classes(class_active_pre, "has-highlighted"),
active_code: classes(class_active_code, ""),
}
}
ConfiguredTransformer::NotationErrorLevel {
class_map,
class_active_pre,
class_active_code,
} => Notation {
class_map: match class_map {
Some(map) => map
.iter()
.map(|(key, value)| (key.clone(), split_classes(value)))
.collect(),
None => ["error", "warning", "info"]
.into_iter()
.map(|level| (level.into(), vec!["highlighted".into(), level.into()]))
.collect(),
},
active_pre: classes(class_active_pre, "has-highlighted"),
active_code: classes(class_active_code, ""),
},
_ => return None,
})
}
fn read_classes(classes: &Option<Classes>, default: &str) -> Vec<String> {
match classes {
Some(classes) => split_classes(classes),
None => split_classes(&Classes::One(default.into())),
}
}
fn split_classes(classes: &Classes) -> Vec<String> {
match classes {
Classes::One(text) => text.split_whitespace().map(str::to_owned).collect(),
Classes::Many(list) => list.clone(),
}
}
fn read_meta_lines(meta: &str) -> Vec<(i64, i64)> {
let Some(captures) = META_LINES.as_ref().and_then(|lines| lines.captures(meta))
else {
return Vec::new();
};
let list = captures.get(1).map_or("", |list| list.as_str());
list.split(',')
.filter_map(|part| {
let range: Vec<Option<i64>> = part.split('-').map(read_integer).collect();
match range[..] {
[Some(single)] => Some((single, single)),
[Some(start), Some(end), ..] => Some((start, end)),
_ => None,
}
})
.collect()
}
fn read_integer(text: &str) -> Option<i64> {
let digits: String = text.chars().take_while(char::is_ascii_digit).collect();
digits.parse().ok()
}