use std::borrow::Cow;
use super::utils::{count_single_backticks, cow_append, ends_with_odd_backslashes};
fn is_inside_incomplete_code_block(text: &str) -> bool {
crate::incomplete_code::has_incomplete_code_fence(text)
}
fn is_inside_unclosed_dollar_block(text: &str, backtick_pos: usize) -> bool {
let bytes = text.as_bytes();
let mut i = 0;
let mut in_block = false;
while i < backtick_pos {
if bytes[i] == b'\\' && i + 1 < bytes.len() {
i += 2;
continue;
}
if bytes[i] == b'$' && i + 1 < bytes.len() && bytes[i + 1] == b'$' {
in_block = !in_block;
i += 2;
continue;
}
i += 1;
}
in_block
}
fn handle_inline_triple_backticks(text: &str) -> Option<Cow<'_, str>> {
if text.contains('\n') || !text.starts_with("```") {
return None;
}
let rest = &text[3..];
if !rest.contains('`') || rest.ends_with("```") {
return None;
}
if text.ends_with("``") {
return Some(cow_append(text, "`"));
}
None
}
pub fn handle(text: &str) -> Cow<'_, str> {
if let Some(result) = handle_inline_triple_backticks(text) {
return result;
}
if is_inside_incomplete_code_block(text) {
return Cow::Borrowed(text);
}
let bytes = text.as_bytes();
let mut last_backtick = None;
let mut i = bytes.len();
while i > 0 {
i -= 1;
if bytes[i] == b'`' {
if (i + 2 < bytes.len() && bytes[i + 1] == b'`' && bytes[i + 2] == b'`')
|| (i >= 1 && bytes[i - 1] == b'`')
{
continue;
}
if i >= 1 && bytes[i - 1] == b'\\' {
continue;
}
last_backtick = Some(i);
break;
}
}
if last_backtick.is_none() {
return Cow::Borrowed(text);
}
if let Some(pos) = last_backtick
&& is_inside_unclosed_dollar_block(text, pos)
{
return Cow::Borrowed(text);
}
if let Some(pos) = last_backtick {
let content = &text[pos + 1..];
if content.is_empty()
|| content.bytes().all(|b| {
matches!(
b,
b' ' | b'\t' | b'\n' | b'_' | b'~' | b'*' | b'`' | b'$' | b'\\'
)
})
{
return Cow::Borrowed(text);
}
}
let count = count_single_backticks(text);
if count % 2 == 1 {
if ends_with_odd_backslashes(text) {
return Cow::Borrowed(text);
}
return cow_append(text, "`");
}
Cow::Borrowed(text)
}
#[cfg(test)]
mod tests {
use super::handle;
use std::borrow::Cow;
#[test]
fn completes_inline_code() {
assert_eq!(handle("`code").as_ref(), "`code`");
}
#[test]
fn leaves_complete_inline_code() {
assert!(matches!(handle("`code`"), Cow::Borrowed(_)));
}
#[test]
fn does_not_complete_in_code_block() {
assert!(matches!(handle("```\n`code\n"), Cow::Borrowed(_)));
}
#[test]
fn empty_content() {
assert!(matches!(handle("`"), Cow::Borrowed(_)));
}
#[test]
fn escaped_backtick() {
assert!(matches!(handle("\\`code"), Cow::Borrowed(_)));
}
#[test]
fn leaves_trailing_backslash() {
assert!(matches!(handle("`\\"), Cow::Borrowed(_)));
}
#[test]
fn idempotent_with_trailing_backslash() {
let once = handle("`\\").into_owned();
let twice = handle(&once).into_owned();
assert_eq!(twice, once);
}
#[test]
fn does_not_complete_inside_unclosed_dollar_block() {
assert!(matches!(handle("$$`code"), Cow::Borrowed(_)));
}
#[test]
fn completes_after_closed_dollar_block() {
assert_eq!(handle("$$x$$`code").as_ref(), "$$x$$`code`");
}
}