From afe360825f635a28d61703dba96579bb0620cc64 Mon Sep 17 00:00:00 2001 From: Leynos Date: Tue, 29 Jul 2025 17:38:42 +0100 Subject: [PATCH 1/3] Refactor wrap_text case handling --- src/wrap.rs | 91 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/src/wrap.rs b/src/wrap.rs index 0eb8e45a..069fe7b3 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -257,6 +257,31 @@ fn wrap_preserving_code(text: &str, width: usize) -> Vec { #[doc(hidden)] pub fn is_fence(line: &str) -> bool { FENCE_RE.is_match(line) } +fn is_list_item(line: &str) -> Option<(&str, &str)> { + BULLET_RE + .captures(line) + .map(|cap| (cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())) +} + +fn is_footnote(line: &str) -> Option<(String, &str)> { + FOOTNOTE_RE.captures(line).map(|cap| { + ( + format!( + "{}{}", + cap.get(1).unwrap().as_str(), + cap.get(2).unwrap().as_str() + ), + cap.get(3).unwrap().as_str(), + ) + }) +} + +fn is_blockquote(line: &str) -> Option<(&str, &str)> { + BLOCKQUOTE_RE + .captures(line) + .map(|cap| (cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())) +} + fn flush_paragraph(out: &mut Vec, buf: &[(String, bool)], indent: &str, width: usize) { if buf.is_empty() { return; @@ -330,6 +355,48 @@ fn handle_prefix_line( append_wrapped_with_prefix(out, prefix, rest, width, repeat_prefix); } +fn handle_list_item( + out: &mut Vec, + buf: &mut Vec<(String, bool)>, + indent: &mut String, + width: usize, + line: &str, +) -> bool { + if let Some((prefix, rest)) = is_list_item(line) { + handle_prefix_line(out, buf, indent, width, prefix, rest, false); + return true; + } + false +} + +fn handle_footnote( + out: &mut Vec, + buf: &mut Vec<(String, bool)>, + indent: &mut String, + width: usize, + line: &str, +) -> bool { + if let Some((prefix, rest)) = is_footnote(line) { + handle_prefix_line(out, buf, indent, width, &prefix, rest, false); + return true; + } + false +} + +fn handle_blockquote( + out: &mut Vec, + buf: &mut Vec<(String, bool)>, + indent: &mut String, + width: usize, + line: &str, +) -> bool { + if let Some((prefix, rest)) = is_blockquote(line) { + handle_prefix_line(out, buf, indent, width, prefix, rest, true); + return true; + } + false +} + /// Wrap text lines to the given width. /// /// # Panics @@ -380,26 +447,10 @@ pub fn wrap_text(lines: &[String], width: usize) -> Vec { continue; } - if let Some(cap) = BULLET_RE.captures(line) { - let prefix = cap.get(1).unwrap().as_str(); - let rest = cap.get(2).unwrap().as_str(); - handle_prefix_line(&mut out, &mut buf, &mut indent, width, prefix, rest, false); - continue; - } - - if let Some(cap) = FOOTNOTE_RE.captures(line) { - let indent_part = cap.get(1).unwrap().as_str(); - let label_part = cap.get(2).unwrap().as_str(); - let prefix = format!("{indent_part}{label_part}"); - let rest = cap.get(3).unwrap().as_str(); - handle_prefix_line(&mut out, &mut buf, &mut indent, width, &prefix, rest, false); - continue; - } - - if let Some(cap) = BLOCKQUOTE_RE.captures(line) { - let prefix = cap.get(1).unwrap().as_str(); - let rest = cap.get(2).unwrap().as_str(); - handle_prefix_line(&mut out, &mut buf, &mut indent, width, prefix, rest, true); + if handle_list_item(&mut out, &mut buf, &mut indent, width, line) + || handle_footnote(&mut out, &mut buf, &mut indent, width, line) + || handle_blockquote(&mut out, &mut buf, &mut indent, width, line) + { continue; } From 3d51f5416239f198bff939ac2ced2624b2f2a502 Mon Sep 17 00:00:00 2001 From: Leynos Date: Tue, 29 Jul 2025 18:09:48 +0100 Subject: [PATCH 2/3] Replace prefix handlers with table --- src/wrap.rs | 125 +++++++++++++++++++++------------------------------- 1 file changed, 51 insertions(+), 74 deletions(-) diff --git a/src/wrap.rs b/src/wrap.rs index 069fe7b3..735ca95f 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -4,7 +4,7 @@ //! `docs/architecture.md` and uses the `unicode-width` crate for accurate //! display calculations. -use regex::Regex; +use regex::{Captures, Regex}; static FENCE_RE: std::sync::LazyLock = std::sync::LazyLock::new(|| Regex::new(r"^\s*(```|~~~).*").unwrap()); @@ -18,6 +18,40 @@ static FOOTNOTE_RE: std::sync::LazyLock = static BLOCKQUOTE_RE: std::sync::LazyLock = std::sync::LazyLock::new(|| Regex::new(r"^(\s*(?:>\s*)+)(.*)$").unwrap()); +struct PrefixHandler { + re: &'static std::sync::LazyLock, + is_bq: bool, + build_prefix: fn(&Captures) -> String, + rest_group: usize, +} + +fn build_bullet_prefix(cap: &Captures) -> String { cap[1].to_string() } + +fn build_footnote_prefix(cap: &Captures) -> String { format!("{}{}", &cap[1], &cap[2]) } + +fn build_blockquote_prefix(cap: &Captures) -> String { cap[1].to_string() } + +static HANDLERS: &[PrefixHandler] = &[ + PrefixHandler { + re: &BULLET_RE, + is_bq: false, + build_prefix: build_bullet_prefix, + rest_group: 2, + }, + PrefixHandler { + re: &FOOTNOTE_RE, + is_bq: false, + build_prefix: build_footnote_prefix, + rest_group: 3, + }, + PrefixHandler { + re: &BLOCKQUOTE_RE, + is_bq: true, + build_prefix: build_blockquote_prefix, + rest_group: 2, + }, +]; + /// Markdown token emitted by [`tokenize_markdown`]. #[derive(Debug, PartialEq)] pub enum Token<'a> { @@ -257,31 +291,6 @@ fn wrap_preserving_code(text: &str, width: usize) -> Vec { #[doc(hidden)] pub fn is_fence(line: &str) -> bool { FENCE_RE.is_match(line) } -fn is_list_item(line: &str) -> Option<(&str, &str)> { - BULLET_RE - .captures(line) - .map(|cap| (cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())) -} - -fn is_footnote(line: &str) -> Option<(String, &str)> { - FOOTNOTE_RE.captures(line).map(|cap| { - ( - format!( - "{}{}", - cap.get(1).unwrap().as_str(), - cap.get(2).unwrap().as_str() - ), - cap.get(3).unwrap().as_str(), - ) - }) -} - -fn is_blockquote(line: &str) -> Option<(&str, &str)> { - BLOCKQUOTE_RE - .captures(line) - .map(|cap| (cap.get(1).unwrap().as_str(), cap.get(2).unwrap().as_str())) -} - fn flush_paragraph(out: &mut Vec, buf: &[(String, bool)], indent: &str, width: usize) { if buf.is_empty() { return; @@ -355,48 +364,6 @@ fn handle_prefix_line( append_wrapped_with_prefix(out, prefix, rest, width, repeat_prefix); } -fn handle_list_item( - out: &mut Vec, - buf: &mut Vec<(String, bool)>, - indent: &mut String, - width: usize, - line: &str, -) -> bool { - if let Some((prefix, rest)) = is_list_item(line) { - handle_prefix_line(out, buf, indent, width, prefix, rest, false); - return true; - } - false -} - -fn handle_footnote( - out: &mut Vec, - buf: &mut Vec<(String, bool)>, - indent: &mut String, - width: usize, - line: &str, -) -> bool { - if let Some((prefix, rest)) = is_footnote(line) { - handle_prefix_line(out, buf, indent, width, &prefix, rest, false); - return true; - } - false -} - -fn handle_blockquote( - out: &mut Vec, - buf: &mut Vec<(String, bool)>, - indent: &mut String, - width: usize, - line: &str, -) -> bool { - if let Some((prefix, rest)) = is_blockquote(line) { - handle_prefix_line(out, buf, indent, width, prefix, rest, true); - return true; - } - false -} - /// Wrap text lines to the given width. /// /// # Panics @@ -408,7 +375,7 @@ pub fn wrap_text(lines: &[String], width: usize) -> Vec { let mut indent = String::new(); let mut in_code = false; - for line in lines { + 'line_loop: for line in lines { if FENCE_RE.is_match(line) { flush_paragraph(&mut out, &buf, &indent, width); buf.clear(); @@ -447,11 +414,21 @@ pub fn wrap_text(lines: &[String], width: usize) -> Vec { continue; } - if handle_list_item(&mut out, &mut buf, &mut indent, width, line) - || handle_footnote(&mut out, &mut buf, &mut indent, width, line) - || handle_blockquote(&mut out, &mut buf, &mut indent, width, line) - { - continue; + for handler in HANDLERS { + if let Some(cap) = handler.re.captures(line) { + let prefix = (handler.build_prefix)(&cap); + let rest = cap.get(handler.rest_group).unwrap().as_str(); + handle_prefix_line( + &mut out, + &mut buf, + &mut indent, + width, + &prefix, + rest, + handler.is_bq, + ); + continue 'line_loop; + } } if buf.is_empty() { From 99181508271f4090a82e351c955671150af60099 Mon Sep 17 00:00:00 2001 From: Leynos Date: Tue, 29 Jul 2025 19:19:40 +0100 Subject: [PATCH 3/3] Move prefix builders inside PrefixHandler --- src/wrap.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/wrap.rs b/src/wrap.rs index 735ca95f..a2a520b5 100644 --- a/src/wrap.rs +++ b/src/wrap.rs @@ -25,29 +25,31 @@ struct PrefixHandler { rest_group: usize, } -fn build_bullet_prefix(cap: &Captures) -> String { cap[1].to_string() } +impl PrefixHandler { + fn build_bullet_prefix(cap: &Captures) -> String { cap[1].to_string() } -fn build_footnote_prefix(cap: &Captures) -> String { format!("{}{}", &cap[1], &cap[2]) } + fn build_footnote_prefix(cap: &Captures) -> String { format!("{}{}", &cap[1], &cap[2]) } -fn build_blockquote_prefix(cap: &Captures) -> String { cap[1].to_string() } + fn build_blockquote_prefix(cap: &Captures) -> String { cap[1].to_string() } +} static HANDLERS: &[PrefixHandler] = &[ PrefixHandler { re: &BULLET_RE, is_bq: false, - build_prefix: build_bullet_prefix, + build_prefix: PrefixHandler::build_bullet_prefix, rest_group: 2, }, PrefixHandler { re: &FOOTNOTE_RE, is_bq: false, - build_prefix: build_footnote_prefix, + build_prefix: PrefixHandler::build_footnote_prefix, rest_group: 3, }, PrefixHandler { re: &BLOCKQUOTE_RE, is_bq: true, - build_prefix: build_blockquote_prefix, + build_prefix: PrefixHandler::build_blockquote_prefix, rest_group: 2, }, ];