use rumdl_lib::config as rumdl_config;
use rumdl_lib::lint_context::LintContext;
use rumdl_lib::rule::Rule;
use rumdl_lib::utils::code_block_utils::CodeBlockUtils;
pub use rumdl_lib::embedded_lint::check_embedded_markdown_blocks;
pub(super) use rumdl_lib::embedded_lint::has_fenced_code_blocks;
pub(super) use rumdl_lib::embedded_lint::should_lint_embedded_markdown;
pub(super) const MAX_EMBEDDED_DEPTH: usize = rumdl_lib::embedded_lint::MAX_EMBEDDED_DEPTH;
pub fn format_embedded_markdown_blocks(
content: &mut String,
rules: &[Box<dyn Rule>],
config: &rumdl_config::Config,
) -> usize {
format_embedded_markdown_blocks_recursive(content, rules, config, 0)
}
fn format_embedded_markdown_blocks_recursive(
content: &mut String,
rules: &[Box<dyn Rule>],
config: &rumdl_config::Config,
depth: usize,
) -> usize {
if depth >= MAX_EMBEDDED_DEPTH {
return 0;
}
if !has_fenced_code_blocks(content) {
return 0;
}
let blocks = CodeBlockUtils::detect_markdown_code_blocks(content);
if blocks.is_empty() {
return 0;
}
let inline_config = rumdl_lib::inline_config::InlineConfig::from_content(content);
let mut formatted_count = 0;
for block in blocks.into_iter().rev() {
let block_content = &content[block.content_start..block.content_end];
if block_content.trim().is_empty() {
continue;
}
let block_line = content[..block.content_start].matches('\n').count() + 1;
let block_rules: Vec<Box<dyn Rule>> = rules
.iter()
.filter(|rule| !inline_config.is_rule_disabled(rule.name(), block_line))
.map(|r| dyn_clone::clone_box(&**r))
.collect();
let (stripped_content, common_indent) = strip_common_indent(block_content);
let mut formatted = stripped_content;
let nested_formatted =
format_embedded_markdown_blocks_recursive(&mut formatted, &block_rules, config, depth + 1);
let ctx = LintContext::new(&formatted, config.markdown_flavor(), None);
let mut warnings = Vec::new();
for rule in &block_rules {
if let Ok(rule_warnings) = rule.check(&ctx) {
warnings.extend(rule_warnings);
}
}
if !warnings.is_empty() {
let _fixed = super::processing::apply_fixes_coordinated(
&block_rules,
&warnings,
&mut formatted,
true,
true,
config,
None,
);
}
let original_had_trailing_newline = block_content.ends_with('\n');
if !original_had_trailing_newline && formatted.ends_with('\n') {
formatted.pop();
}
let restored = restore_indent(&formatted, &common_indent);
if restored != block_content {
content.replace_range(block.content_start..block.content_end, &restored);
formatted_count += 1;
}
formatted_count += nested_formatted;
}
formatted_count
}
pub(super) fn strip_common_indent(content: &str) -> (String, String) {
rumdl_lib::embedded_lint::strip_common_indent(content)
}
pub(super) fn restore_indent(content: &str, indent: &str) -> String {
let has_trailing_newline = content.ends_with('\n');
let mut result: String = content
.lines()
.map(|line| {
if line.trim().is_empty() {
line.to_string()
} else {
format!("{indent}{line}")
}
})
.collect::<Vec<_>>()
.join("\n");
if has_trailing_newline && !result.ends_with('\n') {
result.push('\n');
}
result
}