use std::borrow::Cow;
use unicode_normalization::UnicodeNormalization;
const LOW_SIGNAL_CONTENT: &[&str] = &[
"ok",
"done",
"done.",
"got it",
"got it.",
"understood",
"understood.",
"sure",
"sure.",
"yes",
"no",
"thanks",
"thanks.",
"thank you",
"thank you.",
];
pub trait Canonicalizer: Send + Sync {
fn canonicalize(&self, text: &str) -> String;
fn canonicalize_query(&self, query: &str) -> String;
}
pub struct DefaultCanonicalizer {
pub max_length: usize,
pub code_head_lines: usize,
pub code_tail_lines: usize,
}
impl Default for DefaultCanonicalizer {
fn default() -> Self {
Self {
max_length: 2000,
code_head_lines: 20,
code_tail_lines: 10,
}
}
}
#[inline]
fn nfc_normalize(text: &str) -> Cow<'_, str> {
if text.is_ascii() {
Cow::Borrowed(text)
} else {
Cow::Owned(text.nfc().collect())
}
}
impl Canonicalizer for DefaultCanonicalizer {
fn canonicalize(&self, text: &str) -> String {
let normalized = nfc_normalize(text);
let stripped = self.strip_markdown_and_code(&normalized);
let ws_normalized = normalize_whitespace(&stripped);
if is_low_signal(&ws_normalized) {
return String::new();
}
truncate_to_chars(&ws_normalized, self.max_length)
}
fn canonicalize_query(&self, query: &str) -> String {
let normalized = nfc_normalize(query);
let trimmed = normalized.trim();
truncate_to_chars(trimmed, self.max_length)
}
}
impl DefaultCanonicalizer {
fn strip_markdown_and_code(&self, text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut in_code_block = false;
let mut code_block_lang = "";
let mut code_lines: Vec<&str> = Vec::new();
for line in text.lines() {
if line.starts_with("```") {
if in_code_block {
push_collapsed_code_block(
&mut result,
code_block_lang,
&code_lines,
self.code_head_lines,
self.code_tail_lines,
);
result.push('\n');
code_lines.clear();
code_block_lang = "";
in_code_block = false;
} else {
in_code_block = true;
code_block_lang = code_block_language(line);
}
} else if in_code_block {
code_lines.push(line);
} else {
let stripped = strip_markdown_line(line);
if !stripped.is_empty() {
result.push_str(&stripped);
result.push('\n');
}
}
}
if in_code_block && !code_lines.is_empty() {
push_collapsed_code_block(
&mut result,
code_block_lang,
&code_lines,
self.code_head_lines,
self.code_tail_lines,
);
result.push('\n');
}
result
}
}
#[inline]
fn code_block_language(line: &str) -> &str {
line.trim_start_matches('`').trim()
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn code_block_language_owned_bench(line: &str) -> String {
code_block_language(line).to_owned()
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn code_block_language_borrowed_bench(line: &str) -> &str {
code_block_language(line)
}
fn push_joined<'a>(out: &mut String, mut lines: impl Iterator<Item = &'a str>) {
if let Some(first) = lines.next() {
out.push_str(first);
for line in lines {
out.push('\n');
out.push_str(line);
}
}
}
fn push_collapsed_code_block(
out: &mut String,
lang: &str,
lines: &[&str],
head: usize,
tail: usize,
) {
use std::fmt::Write as _;
let collapse = lines.len() > head + tail;
out.push('[');
if lang.is_empty() {
out.push_str("code");
} else {
out.push_str("code: ");
out.push_str(lang);
}
out.push_str("]\n");
if collapse {
push_joined(out, lines.iter().take(head).copied());
let omitted = lines.len() - head - tail;
let _ = write!(out, "\n[... {omitted} lines omitted ...]\n");
push_joined(out, lines.iter().skip(lines.len() - tail).copied());
} else {
push_joined(out, lines.iter().copied());
}
}
#[cfg(any(test, feature = "bench-internals"))]
fn collapse_code_block(lang: &str, lines: &[&str], head: usize, tail: usize) -> String {
let collapse = lines.len() > head + tail;
let label_len = if lang.is_empty() { 4 } else { 6 + lang.len() };
let kept_bytes: usize = if collapse {
lines
.iter()
.take(head)
.chain(lines.iter().skip(lines.len() - tail))
.map(|line| line.len() + 1)
.sum()
} else {
lines.iter().map(|line| line.len() + 1).sum()
};
let mut out = String::with_capacity(label_len + 3 + kept_bytes + if collapse { 32 } else { 0 });
push_collapsed_code_block(&mut out, lang, lines, head, tail);
out
}
#[cfg(any(test, feature = "bench-internals"))]
#[doc(hidden)]
#[must_use]
pub fn collapse_code_block_slow(lang: &str, lines: &[&str], head: usize, tail: usize) -> String {
let lang_label = if lang.is_empty() {
"code".to_string()
} else {
format!("code: {lang}")
};
if lines.len() <= head + tail {
format!("[{lang_label}]\n{}", lines.join("\n"))
} else {
let head_part: Vec<_> = lines.iter().take(head).copied().collect();
let tail_part: Vec<_> = lines.iter().skip(lines.len() - tail).copied().collect();
let omitted = lines.len() - head - tail;
format!(
"[{lang_label}]\n{}\n[... {omitted} lines omitted ...]\n{}",
head_part.join("\n"),
tail_part.join("\n")
)
}
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn collapse_code_block_fast_bench(
lang: &str,
lines: &[&str],
head: usize,
tail: usize,
) -> String {
collapse_code_block(lang, lines, head, tail)
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
pub fn push_collapsed_code_block_fast_bench(
out: &mut String,
lang: &str,
lines: &[&str],
head: usize,
tail: usize,
) {
push_collapsed_code_block(out, lang, lines, head, tail);
}
fn strip_markdown_line(line: &str) -> Cow<'_, str> {
let mut has_star = false;
let mut has_underscore = false;
let mut has_backtick = false;
let mut has_bracket = false;
for b in line.bytes() {
match b {
b'*' => has_star = true,
b'_' => has_underscore = true,
b'`' => has_backtick = true,
b'[' => has_bracket = true,
_ => {}
}
}
if !(has_star || has_underscore || has_backtick || has_bracket) {
return strip_prefixes_and_list_marker(line);
}
let mut r: Cow<'_, str> = Cow::Borrowed(line);
if has_star {
r = Cow::Owned(r.replace("**", "")); }
if has_underscore {
r = Cow::Owned(r.replace("__", "")); }
if has_star {
r = Cow::Owned(r.replace('*', "")); }
if has_underscore && let Some(stripped) = strip_italic_underscores(&r) {
r = Cow::Owned(stripped); }
if has_backtick {
r = Cow::Owned(r.replace('`', "")); }
if has_bracket {
r = Cow::Owned(strip_markdown_links(&r)); }
Cow::Owned(strip_prefixes_and_list_marker(&r).into_owned())
}
fn strip_prefixes_and_list_marker(s: &str) -> Cow<'_, str> {
let prefix_stripped = s
.trim_start_matches('#')
.trim_start()
.trim_start_matches('>')
.trim_start();
strip_list_marker(prefix_stripped)
}
fn strip_italic_underscores(text: &str) -> Option<String> {
let is_word = |c: char| c.is_alphanumeric() || c == '_';
let mut result: Option<String> = None;
let mut prev: Option<char> = None;
let mut chars = text.char_indices().peekable();
while let Some((idx, c)) = chars.next() {
let drop_marker = c == '_' && {
let prev_is_word = prev.is_some_and(|p| is_word(p) && p != '_');
let next_is_word = chars.peek().is_some_and(|&(_, n)| is_word(n) && n != '_');
(!prev_is_word && next_is_word) || (prev_is_word && !next_is_word)
};
if drop_marker {
result.get_or_insert_with(|| {
let mut buf = String::with_capacity(text.len());
buf.push_str(&text[..idx]);
buf
});
} else if let Some(buf) = result.as_mut() {
buf.push(c);
}
prev = Some(c);
}
result
}
fn strip_markdown_links(text: &str) -> String {
let bytes = text.as_bytes();
let mut result = String::with_capacity(text.len());
let mut cursor = 0;
while let Some(relative_open) = text[cursor..].find('[') {
let open = cursor + relative_open;
result.push_str(&text[cursor..open]);
let mut bracket_depth = 1_usize;
let mut scan = open + 1;
let mut label_close = None;
while scan < bytes.len() {
match bytes[scan] {
b'[' => bracket_depth += 1,
b']' => {
bracket_depth -= 1;
if bracket_depth == 0 {
label_close = Some(scan);
break;
}
}
_ => {}
}
scan += 1;
}
let Some(close) = label_close else {
result.push_str(&text[open..]);
return result;
};
if bytes.get(close + 1) == Some(&b'(') {
let mut paren_depth = 1_usize;
scan = close + 2;
let mut url_close = None;
while scan < bytes.len() {
match bytes[scan] {
b'(' => paren_depth += 1,
b')' => {
paren_depth -= 1;
if paren_depth == 0 {
url_close = Some(scan);
break;
}
}
_ => {}
}
scan += 1;
}
let Some(end) = url_close else {
result.push_str(&text[open..]);
return result;
};
result.push_str(&text[open + 1..close]);
cursor = end + 1;
} else {
result.push_str(&text[open..=close]);
cursor = close + 1;
}
}
result.push_str(&text[cursor..]);
result
}
#[cfg(any(test, feature = "bench-internals"))]
fn strip_markdown_links_reused_buffers(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut link_text = String::new();
let mut url_part = String::new();
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
link_text.clear();
let mut found_close = false;
let mut bracket_depth = 1;
for inner in chars.by_ref() {
if inner == '[' {
bracket_depth += 1;
} else if inner == ']' {
bracket_depth -= 1;
if bracket_depth == 0 {
found_close = true;
break;
}
}
link_text.push(inner);
}
if found_close && chars.peek() == Some(&'(') {
chars.next(); url_part.clear();
url_part.push('(');
let mut depth = 1;
let mut valid_link = false;
for inner in chars.by_ref() {
url_part.push(inner);
match inner {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
valid_link = true;
break;
}
}
_ => {}
}
}
if valid_link {
result.push_str(&link_text);
} else {
result.push('[');
result.push_str(&link_text);
result.push(']');
result.push_str(&url_part);
}
} else {
result.push('[');
result.push_str(&link_text);
if found_close {
result.push(']');
}
}
} else {
result.push(c);
}
}
result
}
#[cfg(any(test, feature = "bench-internals"))]
fn strip_markdown_links_fresh_buffers(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
let mut link_text = String::new();
let mut found_close = false;
let mut bracket_depth = 1;
for inner in chars.by_ref() {
if inner == '[' {
bracket_depth += 1;
} else if inner == ']' {
bracket_depth -= 1;
if bracket_depth == 0 {
found_close = true;
break;
}
}
link_text.push(inner);
}
if found_close && chars.peek() == Some(&'(') {
chars.next();
let mut url_part = String::from("(");
let mut depth = 1;
let mut valid_link = false;
for inner in chars.by_ref() {
url_part.push(inner);
match inner {
'(' => depth += 1,
')' => {
depth -= 1;
if depth == 0 {
valid_link = true;
break;
}
}
_ => {}
}
}
if valid_link {
result.push_str(&link_text);
} else {
result.push('[');
result.push_str(&link_text);
result.push(']');
result.push_str(&url_part);
}
} else {
result.push('[');
result.push_str(&link_text);
if found_close {
result.push(']');
}
}
} else {
result.push(c);
}
}
result
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn strip_markdown_links_fresh_buffers_bench(text: &str) -> String {
strip_markdown_links_fresh_buffers(text)
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn strip_markdown_links_reused_buffers_bench(text: &str) -> String {
strip_markdown_links_reused_buffers(text)
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn strip_markdown_links_source_slices_bench(text: &str) -> String {
strip_markdown_links(text)
}
fn strip_list_marker(line: &str) -> Cow<'_, str> {
let trimmed = line.trim_start();
if let Some(rest) = trimmed.strip_prefix("- ") {
return Cow::Borrowed(rest);
}
if let Some(rest) = trimmed.strip_prefix("+ ") {
return Cow::Borrowed(rest);
}
let mut chars = trimmed.chars().peekable();
let mut digit_count = 0;
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() {
digit_count += 1;
chars.next();
} else {
break;
}
}
if digit_count > 0 && chars.next() == Some('.') && chars.peek() == Some(&' ') {
return Cow::Borrowed(&trimmed[digit_count + 2..]);
}
Cow::Borrowed(line)
}
fn normalize_whitespace(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut prev_whitespace = true; let bytes = text.as_bytes();
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b < 0x80 {
if b == 0x0B || b.is_ascii_whitespace() {
if !prev_whitespace {
result.push(' ');
prev_whitespace = true;
}
} else {
result.push(char::from(b));
prev_whitespace = false;
}
i += 1;
} else {
let ch = text[i..]
.chars()
.next()
.unwrap_or(char::REPLACEMENT_CHARACTER);
let len = ch.len_utf8();
if ch.is_whitespace() {
if !prev_whitespace {
result.push(' ');
prev_whitespace = true;
}
} else {
result.push(ch);
prev_whitespace = false;
}
i += len;
}
}
let trimmed_len = result.trim_end().len();
result.truncate(trimmed_len);
result
}
#[cfg(any(test, feature = "bench-internals"))]
#[doc(hidden)]
#[must_use]
pub fn normalize_whitespace_slow(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut prev_whitespace = true;
for c in text.chars() {
if c.is_whitespace() {
if !prev_whitespace {
result.push(' ');
prev_whitespace = true;
}
} else {
result.push(c);
prev_whitespace = false;
}
}
let trimmed_len = result.trim_end().len();
result.truncate(trimmed_len);
result
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn normalize_whitespace_fast_bench(text: &str) -> String {
normalize_whitespace(text)
}
fn is_low_signal(text: &str) -> bool {
let trimmed = text.trim();
LOW_SIGNAL_CONTENT
.iter()
.any(|pattern| trimmed.eq_ignore_ascii_case(pattern))
}
fn truncate_to_chars(text: &str, max_chars: usize) -> String {
if text.len() <= max_chars {
return text.to_owned();
}
if text.as_bytes()[..max_chars].is_ascii() {
return text[..max_chars].to_owned();
}
for (count, (idx, _)) in text.char_indices().enumerate() {
if count == max_chars {
return text[..idx].to_owned();
}
}
text.to_owned()
}
#[cfg(any(test, feature = "bench-internals"))]
#[doc(hidden)]
#[must_use]
pub fn truncate_to_chars_slow(text: &str, max_chars: usize) -> String {
if text.len() <= max_chars {
return text.to_owned();
}
for (count, (idx, _)) in text.char_indices().enumerate() {
if count == max_chars {
return text[..idx].to_owned();
}
}
text.to_owned()
}
#[cfg(feature = "bench-internals")]
#[doc(hidden)]
#[must_use]
pub fn truncate_to_chars_fast_bench(text: &str, max_chars: usize) -> String {
truncate_to_chars(text, max_chars)
}
#[cfg(test)]
mod tests {
use std::fmt::Write;
use super::*;
#[test]
fn truncate_to_chars_matches_slow() {
let ascii = "abcdefghij".repeat(300); let uni = "aéb日cé".repeat(300); let lead = format!("{}{}", "x".repeat(50), "é".repeat(50)); for text in [
"",
"hi",
"hello world",
ascii.as_str(),
uni.as_str(),
lead.as_str(),
] {
for max_chars in [0usize, 1, 3, 49, 50, 51, 100, 500, 2000, 100_000] {
assert_eq!(
truncate_to_chars(text, max_chars),
truncate_to_chars_slow(text, max_chars),
"text.len()={} max_chars={max_chars}",
text.len()
);
}
}
}
#[test]
fn collapse_code_block_matches_slow() {
let body: Vec<&str> = (0..50)
.map(|i| ["fn main() {", " let x = 1;", "}", ""][i % 4])
.collect();
let langs = ["", "rust", "python-with-a-long-name"];
let shapes: &[(&[&str], usize, usize)] = &[
(&[], 20, 10),
(&["only one line"], 20, 10),
(&body[..5], 20, 10), (&body[..30], 20, 10), (&body[..31], 20, 10), (&body[..], 20, 10), (&body[..], 0, 5), (&body[..], 5, 0), (&body[..], 0, 0), ];
for lang in langs {
for &(lines, head, tail) in shapes {
assert_eq!(
collapse_code_block(lang, lines, head, tail),
collapse_code_block_slow(lang, lines, head, tail),
"lang={lang:?} len={} head={head} tail={tail}",
lines.len()
);
let prefix = "before\n";
let expected = format!("{prefix}{}", collapse_code_block(lang, lines, head, tail));
let mut appended = prefix.to_owned();
push_collapsed_code_block(&mut appended, lang, lines, head, tail);
assert_eq!(
appended,
expected,
"append parity: lang={lang:?} len={} head={head} tail={tail}",
lines.len()
);
}
}
}
#[test]
fn normalize_whitespace_matches_slow() {
let cases = [
"",
" ",
"hello world",
" leading and collapsed\ttabs\n\n and trailing ",
"a\u{0B}b\u{0C}c\u{0D}d\u{09}e", "no\u{00A0}break\u{00A0}space", "next\u{0085}line and \u{3000}ideographic",
"café déjà\tvu \u{2003}em space",
"日本語 テスト", "mix\u{0B}\u{00A0}\u{09}collapse",
"trailing unicode ws\u{00A0}",
];
for text in cases {
assert_eq!(
normalize_whitespace(text),
normalize_whitespace_slow(text),
"mismatch for {text:?}"
);
}
}
#[test]
fn nfc_normalization() {
let canon = DefaultCanonicalizer::default();
let input = "caf\u{0065}\u{0301}";
let result = canon.canonicalize(input);
assert!(result.contains("caf\u{00e9}"));
}
#[test]
fn nfc_normalize_ascii_fast_path_matches_reference() {
use unicode_normalization::UnicodeNormalization;
let cases = [
"",
"plain ascii text 123 _-./",
"fn main() { let x = 0; }",
"café\u{0301}", "caf\u{0065}\u{0301}\u{00e9}", "日本語テキスト", "naïve façade",
];
for c in cases {
let reference: String = c.nfc().collect();
assert_eq!(nfc_normalize(c), reference, "input={c:?}");
if c.is_ascii() {
assert_eq!(nfc_normalize(c), c.to_owned(), "ascii fast path {c:?}");
}
}
}
#[test]
fn strip_markdown_headings() {
let canon = DefaultCanonicalizer::default();
let input = "## Heading\nText";
let result = canon.canonicalize(input);
assert!(result.contains("Heading"));
assert!(!result.contains("##"));
}
#[test]
fn strip_markdown_preserves_inline_hash_tokens() {
let canon = DefaultCanonicalizer::default();
let input = "C# and #hashtag\n## Heading";
let result = canon.canonicalize(input);
assert!(result.contains("C#"));
assert!(result.contains("#hashtag"));
assert!(result.contains("Heading"));
assert!(!result.contains("## "));
}
#[test]
fn strip_markdown_bold_italic() {
let canon = DefaultCanonicalizer::default();
let input = "**bold** and *italic* and __underline__";
let result = canon.canonicalize(input);
assert!(result.contains("bold"));
assert!(result.contains("italic"));
assert!(!result.contains("**"));
assert!(!result.contains("__"));
}
#[test]
fn strip_markdown_links() {
let canon = DefaultCanonicalizer::default();
let input = "See [the docs](https://example.com/path) for details";
let result = canon.canonicalize(input);
assert!(result.contains("the docs"));
assert!(!result.contains("https://example.com"));
}
#[test]
fn strip_markdown_link_source_slices_match_former_paths() {
for input in [
"",
"plain text",
"See [the docs](https://example.com/path) for details",
"[one](a) [two [nested]](b(c)) [three](d)",
"] before [empty]() after",
"prefix [closed only] suffix",
"prefix [unclosed suffix",
"Check [link](url( unbalanced. Next sentence.",
"Unicode [café 日](https://example.test/é) tail",
] {
assert_eq!(
super::strip_markdown_links(input),
strip_markdown_links_reused_buffers(input),
"source slices vs scratch reuse, input={input:?}",
);
assert_eq!(
strip_markdown_links_reused_buffers(input),
strip_markdown_links_fresh_buffers(input),
"input={input:?}",
);
}
}
#[test]
fn strip_inline_code_backticks() {
let canon = DefaultCanonicalizer::default();
let input = "Use `fn main()` to start.";
let result = canon.canonicalize(input);
assert!(result.contains("fn main()"));
assert!(!result.contains('`'));
}
#[test]
fn strip_blockquotes() {
let canon = DefaultCanonicalizer::default();
let input = "> This is a quote\n> spanning multiple lines";
let result = canon.canonicalize(input);
assert!(result.contains("This is a quote"));
assert!(!result.starts_with('>'));
}
#[test]
fn strip_list_markers_ordered() {
let canon = DefaultCanonicalizer::default();
let input = "1. First item\n2. Second item\n10. Tenth item";
let result = canon.canonicalize(input);
assert!(result.contains("First item"));
assert!(result.contains("Second item"));
assert!(result.contains("Tenth item"));
}
#[test]
fn strip_list_markers_unordered() {
let canon = DefaultCanonicalizer::default();
let input = "- First\n+ Second";
let result = canon.canonicalize(input);
assert!(result.contains("First"));
assert!(result.contains("Second"));
}
#[test]
fn numbers_not_list_markers_preserved() {
let canon = DefaultCanonicalizer::default();
let input = "3.14159 is pi";
let result = canon.canonicalize(input);
assert!(result.contains("3.14159"));
}
#[test]
fn collapse_short_code_block() {
let canon = DefaultCanonicalizer::default();
let input = "text\n```\nline1\nline2\nline3\n```\nmore text";
let result = canon.canonicalize(input);
assert!(result.contains("line1"));
assert!(result.contains("line3"));
assert!(result.contains("[code]"));
assert!(!result.contains("omitted"));
}
#[test]
fn collapse_long_code_block() {
let mut input = String::from("before\n```\n");
for i in 0..50 {
let _ = writeln!(input, "code line {i}");
}
input.push_str("```\nafter");
let canon = DefaultCanonicalizer::default();
let result = canon.canonicalize(&input);
assert!(result.contains("code line 0"));
assert!(result.contains("code line 19"));
assert!(result.contains("lines omitted"));
assert!(result.contains("code line 40"));
assert!(result.contains("code line 49"));
assert!(!result.contains("code line 25"));
}
#[test]
fn whitespace_normalization() {
let canon = DefaultCanonicalizer::default();
let input = "hello world\n\n\nwith multiple spaces";
let result = canon.canonicalize(input);
assert!(!result.contains(" "));
assert!(result.contains("hello"));
assert!(result.contains("world"));
}
#[test]
fn strip_italic_underscores_matches_reference() {
fn reference(text: &str) -> String {
let chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut keep = vec![true; n];
let is_word = |c: char| c.is_alphanumeric() || c == '_';
for i in 0..n {
if chars[i] != '_' {
continue;
}
let prev_is_word = i > 0 && is_word(chars[i - 1]) && chars[i - 1] != '_';
let next_is_word = i + 1 < n && is_word(chars[i + 1]) && chars[i + 1] != '_';
if (!prev_is_word && next_is_word) || (prev_is_word && !next_is_word) {
keep[i] = false;
}
}
chars
.into_iter()
.zip(keep)
.filter_map(|(c, k)| if k { Some(c) } else { None })
.collect()
}
let cases = [
"",
"_",
"__",
"snake_case_variable",
"_italic_",
"_emphasized text_",
"a _b_ c",
"leading_ and _trailing",
"mixed snake_case and _italic_ together",
"fn compute_value(a_b, c_d) -> retry_count",
"naïve_façade_test", "x_1_2_y",
"_a_b_c_",
"trailing_",
"_leading",
];
for c in cases {
let reference_out = reference(c);
let got = strip_italic_underscores(c).unwrap_or_else(|| c.to_owned());
assert_eq!(got, reference_out, "input={c:?}");
assert_eq!(
strip_italic_underscores(c).is_none(),
reference_out == c,
"borrow-elision parity for input={c:?}"
);
}
}
#[test]
fn low_signal_filtered() {
let canon = DefaultCanonicalizer::default();
assert_eq!(canon.canonicalize("OK"), "");
assert_eq!(canon.canonicalize("Done."), "");
assert_eq!(canon.canonicalize("Got it."), "");
assert_eq!(canon.canonicalize("Thanks!"), "Thanks!"); }
#[test]
fn truncate_long_text() {
let canon = DefaultCanonicalizer {
max_length: 50,
..Default::default()
};
let input = "a".repeat(100);
let result = canon.canonicalize(&input);
assert_eq!(result.chars().count(), 50);
}
#[test]
fn truncate_at_char_boundary() {
let canon = DefaultCanonicalizer {
max_length: 4,
..Default::default()
};
let input = "café!extra";
let result = canon.canonicalize(input);
assert!(result.chars().count() <= 4);
}
#[test]
fn query_canonicalization_trims() {
let canon = DefaultCanonicalizer::default();
let result = canon.canonicalize_query(" hello world ");
assert_eq!(result, "hello world");
}
#[test]
fn query_canonicalization_nfc() {
let canon = DefaultCanonicalizer::default();
let input = "caf\u{0065}\u{0301}";
let result = canon.canonicalize_query(input);
assert!(result.contains("caf\u{00e9}"));
}
#[test]
fn empty_input() {
let canon = DefaultCanonicalizer::default();
let result = canon.canonicalize("");
assert_eq!(result, "");
}
#[test]
fn unclosed_code_block() {
let canon = DefaultCanonicalizer::default();
let input = "text\n```\ncode line 1\ncode line 2";
let result = canon.canonicalize(input);
assert!(result.contains("code line 1"));
assert!(result.contains("code line 2"));
}
#[test]
fn default_config_exact_values() {
let canon = DefaultCanonicalizer::default();
assert_eq!(canon.max_length, 2000);
assert_eq!(canon.code_head_lines, 20);
assert_eq!(canon.code_tail_lines, 10);
}
#[test]
fn multiple_code_blocks_independently_collapsed() {
let mut input = String::from("intro\n```\n");
for i in 0..5 {
let _ = writeln!(input, "block1 line {i}");
}
input.push_str("```\nmiddle text\n```\n");
for i in 0..5 {
let _ = writeln!(input, "block2 line {i}");
}
input.push_str("```\nend");
let canon = DefaultCanonicalizer::default();
let result = canon.canonicalize(&input);
assert!(result.contains("block1 line 0"));
assert!(result.contains("block2 line 0"));
assert!(result.contains("middle text"));
}
#[test]
fn nested_markdown_bold_inside_link() {
let canon = DefaultCanonicalizer::default();
let input = "See [**important** docs](https://example.com) here";
let result = canon.canonicalize(input);
assert!(result.contains("important"));
assert!(result.contains("docs"));
assert!(!result.contains("https://"));
}
#[test]
fn all_heading_levels_stripped() {
let canon = DefaultCanonicalizer::default();
let input = "# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6";
let result = canon.canonicalize(input);
assert!(result.contains("H1"));
assert!(result.contains("H6"));
}
#[test]
fn language_tagged_code_block() {
let canon = DefaultCanonicalizer::default();
let input = "text\n```rust\nfn main() {}\n```\nmore";
let result = canon.canonicalize(input);
assert!(result.contains("[code: rust]"));
assert!(result.contains("fn main()"));
assert!(result.contains("more"));
}
#[test]
fn blank_lines_collapsed_via_whitespace_normalization() {
let canon = DefaultCanonicalizer::default();
let input = "paragraph one\n\nparagraph two";
let result = canon.canonicalize(input);
assert!(result.contains("paragraph one"));
assert!(result.contains("paragraph two"));
}
#[test]
fn query_truncation_respects_max_length() {
let canon = DefaultCanonicalizer {
max_length: 10,
..Default::default()
};
let result = canon.canonicalize_query("a very long query that should be truncated");
assert!(result.chars().count() <= 10);
}
#[test]
fn canonicalizer_trait_is_object_safe() {
let canon: Box<dyn Canonicalizer> = Box::new(DefaultCanonicalizer::default());
let result = canon.canonicalize("## Hello **world**");
assert!(result.contains("Hello"));
assert!(result.contains("world"));
assert!(!result.contains("##"));
}
#[test]
fn large_document_pipeline_completes() {
let canon = DefaultCanonicalizer::default();
let mut input = String::new();
for i in 0..500 {
let _ = writeln!(input, "Line {i} with some content for testing");
}
let result = canon.canonicalize(&input);
assert!(result.chars().count() <= canon.max_length);
assert!(!result.is_empty());
}
#[test]
fn emoji_preserved() {
let canon = DefaultCanonicalizer::default();
let input = "Hello 👋 World 🌍";
let result = canon.canonicalize(input);
assert!(result.contains('👋'));
assert!(result.contains('🌍'));
}
#[test]
fn nested_markdown_links_with_parens() {
let canon = DefaultCanonicalizer::default();
let input = "See [link with (parens)](http://example.com/path(1))";
let result = canon.canonicalize(input);
assert!(result.contains("link with (parens)"));
assert!(!result.contains("http"));
}
#[test]
fn unbalanced_link_preserves_content() {
let canon = DefaultCanonicalizer::default();
let input = "Check [link](url( unbalanced. Next sentence.";
let result = canon.canonicalize(input);
assert!(
result.contains("Next sentence"),
"Should not swallow content"
);
assert!(result.contains("unbalanced"), "Should not swallow content");
}
}