use std::borrow::Cow;
#[must_use]
pub fn strip_markdown_fences(input: &str) -> &str {
let trimmed = input.trim();
let Some(after_open) = trimmed.strip_prefix("```") else {
return input;
};
if !trimmed.ends_with("```") {
return input;
}
let after_lang = after_open.split_once('\n').map_or("", |(_, rest)| rest);
let Some(body) = after_lang.strip_suffix("```") else {
return input;
};
body.strip_suffix('\n').unwrap_or(body)
}
#[must_use]
pub fn strip_outer_braces(input: &str) -> &str {
let trimmed = input.trim();
if !(trimmed.starts_with('{') && trimmed.ends_with('}')) {
return input;
}
let mut depth: i32 = 0;
let mut close_pos = None;
for (i, ch) in trimmed.char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
close_pos = Some(i);
break;
}
}
_ => {}
}
}
match close_pos {
Some(pos) if pos == trimmed.len() - 1 => {
&trimmed[1..trimmed.len() - 1]
}
_ => input,
}
}
#[must_use]
pub fn normalize_line_endings(input: &str) -> Cow<'_, str> {
if input.contains("\r\n") {
Cow::Owned(input.replace("\r\n", "\n"))
} else {
Cow::Borrowed(input)
}
}
#[must_use]
pub fn normalize_for_body_replace(input: &str) -> String {
let step1 = strip_markdown_fences(input);
let step2 = strip_outer_braces(step1);
let step3 = normalize_line_endings(step2).into_owned();
anchor_to_column_zero(&step3)
}
#[must_use]
pub fn normalize_for_full_replace(input: &str) -> String {
let step1 = strip_markdown_fences(input);
normalize_line_endings(step1).into_owned()
}
#[must_use]
pub fn anchor_to_column_zero(code: &str) -> String {
let all_empty = code.lines().all(|l| l.trim().is_empty());
if all_empty {
return String::new();
}
let first_indent = code
.lines()
.find(|l| !l.trim().is_empty())
.map_or(0, |l| l.len() - l.trim_start().len());
if first_indent == 0 {
return code.to_owned();
}
code.lines()
.map(|line| {
let expanded = crate::indent::expand_tabs(line);
let leading_spaces = expanded.len() - expanded.trim_start().len();
if expanded.trim().is_empty() {
String::new() } else if leading_spaces >= first_indent {
expanded[first_indent..].to_owned()
} else {
expanded.trim_start().to_owned()
}
})
.collect::<Vec<String>>()
.join("\n")
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_strip_markdown_fences_with_lang() {
let input = "```rust\nfn hello() {}\n```";
assert_eq!(strip_markdown_fences(input), "fn hello() {}");
}
#[test]
fn test_strip_markdown_fences_no_lang() {
let input = "```\nfn hello() {}\n```";
assert_eq!(strip_markdown_fences(input), "fn hello() {}");
}
#[test]
fn test_strip_markdown_fences_passthrough_no_fences() {
let input = "fn hello() {}";
assert_eq!(strip_markdown_fences(input), "fn hello() {}");
}
#[test]
fn test_strip_markdown_fences_passthrough_partial() {
let input = "```rust\nfn hello() {}";
assert_eq!(strip_markdown_fences(input), input);
}
#[test]
fn test_strip_markdown_fences_opening_only_no_closing() {
let input = "```rust\nfn main() {}\n// no closing fence";
let result = strip_markdown_fences(input);
assert_eq!(result, input); }
#[test]
fn test_strip_markdown_fences_multiline() {
let input = "```typescript\nconst x = 1;\nconst y = 2;\n```";
assert_eq!(strip_markdown_fences(input), "const x = 1;\nconst y = 2;");
}
#[test]
fn test_strip_outer_braces_simple() {
let input = "{ return 42; }";
assert_eq!(strip_outer_braces(input), " return 42; ");
}
#[test]
fn test_strip_outer_braces_multiline() {
let input = "{\n x := 1\n return x\n}";
let result = strip_outer_braces(input);
assert_eq!(result, "\n x := 1\n return x\n");
}
#[test]
fn test_strip_outer_braces_nested_inner_preserved() {
let input = "{ if (x) { y } }";
let result = strip_outer_braces(input);
assert_eq!(result, " if (x) { y } ");
}
#[test]
fn test_strip_outer_braces_not_wrapped() {
let input = "return 42;";
assert_eq!(strip_outer_braces(input), "return 42;");
}
#[test]
fn test_strip_outer_braces_unmatched() {
let input = "{ x } something }";
assert_eq!(strip_outer_braces(input), "{ x } something }");
}
#[test]
fn test_normalize_crlf_to_lf() {
let input = "line1\r\nline2\r\nline3";
let result = normalize_line_endings(input);
assert_eq!(result.as_ref(), "line1\nline2\nline3");
}
#[test]
fn test_normalize_already_lf_is_borrowed() {
let input = "line1\nline2";
let result = normalize_line_endings(input);
assert!(matches!(result, Cow::Borrowed(_)));
}
#[test]
fn test_normalize_full_pipeline_fence_and_braces() {
let input = "```go\n{ return 42; }\n```";
let result = normalize_for_body_replace(input);
assert_eq!(result, "return 42; ");
}
#[test]
fn test_normalize_full_pipeline_plain_code() {
let input = "x := compute()\nreturn x";
let result = normalize_for_body_replace(input);
assert_eq!(result, "x := compute()\nreturn x");
}
#[test]
fn test_normalize_full_pipeline_crlf() {
let input = "x := 1\r\nreturn x";
let result = normalize_for_body_replace(input);
assert_eq!(result, "x := 1\nreturn x");
}
#[test]
fn test_normalize_body_replace_anchors_nested_blocks() {
let input = " let greeting = if name.is_empty() {\n \"Hello, stranger!\".to_owned()\n } else {\n format!(\"Hello, {}!\", name)\n };\n greeting";
let result = normalize_for_body_replace(input);
assert!(
result.starts_with("let greeting = if name.is_empty() {"),
"code should be anchored at column 0"
);
assert!(
result.contains(" \"Hello, stranger!\".to_owned()"),
"nested content should be at column 4 (relative to if block at column 0)"
);
assert!(
result.contains("} else {"),
"else branch should be at column 0"
);
}
#[test]
fn test_strip_markdown_fences_inline_no_newline_returns_input() {
let input = "```code```";
assert_eq!(
strip_markdown_fences(input),
input,
"inline fence with no newline must be returned unchanged"
);
}
#[test]
fn test_strip_markdown_fences_only_opening_and_closing_no_body() {
let input = "```\n```";
let result = strip_markdown_fences(input);
assert_eq!(result, "", "empty-body fence must strip to empty string");
}
#[test]
fn test_normalize_for_full_replace_does_not_strip_braces() {
let input = "{ return 42; }";
let result = normalize_for_full_replace(input);
assert_eq!(
result, input,
"normalize_for_full_replace must preserve outer braces"
);
}
#[test]
fn test_normalize_for_full_replace_strips_fence_and_crlf() {
let input = "```go\r\nfunc Hello() {}\n```";
let result = normalize_for_full_replace(input);
assert_eq!(result, "func Hello() {}");
}
#[test]
fn test_anchor_to_column_zero_nested_if_else() {
let input =
" let x = if cond {\n val1\n } else {\n val2\n };\n result";
let result = anchor_to_column_zero(input);
assert!(
result.starts_with("let x = if cond {"),
"first line should be at column 0"
);
assert!(
result.contains(" val1"),
"val1 should be at column 4 (was 8, shifted by 4)"
);
assert!(
result.contains("} else {"),
"else branch should be at column 0"
);
assert!(result.contains(" val2"), "val2 should be at column 4");
assert!(
result.ends_with("result"),
"last line should be at column 0"
);
}
#[test]
fn test_anchor_preserves_relative_indent() {
let input = " line1\n nested\n line3";
let result = anchor_to_column_zero(input);
assert_eq!(result, "line1\n nested\nline3");
}
#[test]
fn test_anchor_already_at_column_zero_is_noop() {
let input = "let x = 1;\nreturn x;";
let result = anchor_to_column_zero(input);
assert_eq!(result, input, "already at column 0 should be unchanged");
}
#[test]
fn test_anchor_empty_string_returns_empty() {
let input = "";
let result = anchor_to_column_zero(input);
assert_eq!(result, "");
}
#[test]
fn test_anchor_only_whitespace_returns_empty() {
let input = " \n \n ";
let result = anchor_to_column_zero(input);
assert_eq!(result, "");
}
#[test]
fn test_anchor_blank_lines_preserved() {
let input = " line1\n\n line2";
let result = anchor_to_column_zero(input);
assert_eq!(result, "line1\n\nline2", "blank lines should be preserved");
}
#[test]
fn test_anchor_mixed_indentation_uses_first_line() {
let input = " first\nzero\n third";
let result = anchor_to_column_zero(input);
assert_eq!(result, "first\nzero\nthird");
}
}