Rust中多行字符串正则无法捕获全部代码块问题求助
问题原因分析
- 正则匹配逻辑问题:你当前的正则表达式大概率没启用全局匹配,或是用了贪婪模式(比如
([\s\S]+)而非非贪婪的([\s\S]+?)),导致第一个匹配就吞噬了后续所有内容,只能捕获第一个代码块。另外,如果正则没正确处理代码块前后的换行或文本边界,也会导致匹配范围错误,让高亮延续到普通文本。 - 高亮处理逻辑缺陷:如果你的代码只处理了第一个匹配结果,没循环遍历所有捕获到的代码块,自然不会处理后续的。另外,若没有在处理完一个代码块后重置高亮状态(或是错误地将整个文本当作单一代码块处理),会导致普通文本被错误应用代码高亮规则。
解决方案
1. 修正正则表达式并全局匹配
使用支持全局匹配的正则,确保能捕获所有代码块。示例正则如下:
use regex::Regex; // 匹配带语言标识的代码块,非贪婪模式,支持全局匹配 let code_block_re = Regex::new(r"```([a-z0-9]+)\n([\s\S]+?)```").unwrap();
这里的([\s\S]+?)是非贪婪匹配,会在遇到第一个```时停止,避免吞噬后续内容。
2. 拆分文本并分别处理
遍历所有匹配的代码块,将原文本拆分为「普通文本段」和「代码块段」,对普通文本直接保留,代码块用syntect高亮后替换,最后拼接所有片段:
use syntect::{ easy::HighlightLines, highlighting::{Style, ThemeSet}, parsing::SyntaxSet, util::{as_24_bit_terminal_escaped, LinesWithEndings}, }; fn highlight_markdown_code_blocks(input: &str) -> String { let syntax_set = SyntaxSet::load_defaults_newlines(); let theme = &ThemeSet::load_defaults().themes["base16-ocean.dark"]; let code_block_re = Regex::new(r"```([a-z0-9]+)\n([\s\S]+?)```").unwrap(); let mut result = String::new(); let mut last_end = 0; // 遍历所有匹配的代码块 for cap in code_block_re.captures_iter(input) { // 添加匹配前的普通文本 result.push_str(&input[last_end..cap.get(0).unwrap().start()]); let lang = cap.get(1).unwrap().as_str(); let code = cap.get(2).unwrap().as_str(); // 获取对应语言的语法,找不到则用plaintext兜底 let syntax = syntax_set.find_syntax_by_name(lang).unwrap_or_else(|| { syntax_set.find_syntax_by_name("plaintext").unwrap() }); // 高亮代码块 let mut highlighter = HighlightLines::new(syntax, theme); let mut highlighted_code = String::new(); for line in LinesWithEndings::from(code) { let ranges: Vec<(Style, &str)> = highlighter.highlight_line(line, &syntax_set).unwrap(); highlighted_code.push_str(&as_24_bit_terminal_escaped(&ranges[..], true)); } // 重新包裹代码块格式(可根据需求调整) result.push_str(&format!("```{}", lang)); result.push_str(&highlighted_code); result.push_str("```"); last_end = cap.get(0).unwrap().end(); } // 添加最后一段普通文本 result.push_str(&input[last_end..]); result }
3. 替代实现:用专业Markdown解析库
正则处理Markdown代码块容易遇到边缘情况(比如代码块内包含```、嵌套代码块等),更可靠的方式是用专门的Markdown解析库,比如pulldown-cmark:
use pulldown_cmark::{Event, Options, Parser, Tag}; use syntect::{ easy::HighlightLines, highlighting::{Style, ThemeSet}, parsing::SyntaxSet, util::{as_24_bit_terminal_escaped, LinesWithEndings}, }; fn highlight_markdown_code_blocks(input: &str) -> String { let syntax_set = SyntaxSet::load_defaults_newlines(); let theme = &ThemeSet::load_defaults().themes["base16-ocean.dark"]; let mut options = Options::empty(); options.insert(Options::ENABLE_STRIKETHROUGH); let parser = Parser::new_ext(input, options); let mut result = String::new(); let mut in_code_block = false; let mut current_syntax = None; let mut current_code = String::new(); for event in parser { match event { Event::Start(Tag::CodeBlock(lang)) => { in_code_block = true; // 解析语言标识,找不到则用plaintext let lang_str = lang.as_ref().map(|s| s.as_str()).unwrap_or("plaintext"); current_syntax = syntax_set.find_syntax_by_name(lang_str) .or_else(|| syntax_set.find_syntax_by_name("plaintext")); } Event::End(Tag::CodeBlock(_)) => { in_code_block = false; // 高亮代码并添加到结果 if let Some(syntax) = current_syntax { let mut highlighter = HighlightLines::new(syntax, theme); let mut highlighted_code = String::new(); for line in LinesWithEndings::from(¤t_code) { let ranges = highlighter.highlight_line(line, &syntax_set).unwrap(); highlighted_code.push_str(&as_24_bit_terminal_escaped(&ranges[..], true)); } result.push_str(&format!("```{}", lang.as_ref().unwrap_or(&"plaintext".into()))); result.push_str(&highlighted_code); result.push_str("```\n"); } current_code.clear(); current_syntax = None; } Event::Text(text) if in_code_block => { current_code.push_str(&text); } Event::Text(text) => { result.push_str(&text); } // 其他Markdown元素(标题、列表等)可按需保留格式 _ => {} } } result }
测试验证
用以下测试文本验证:
这是普通文本 ```rust fn main() { println!("Hello World!"); }
这是中间的普通文本
print("Hello Python")
这是最后一段普通文本
修正后的代码应该能正确高亮两个代码块,且中间和末尾的普通文本不会被错误高亮。 内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

