fn normalize_escapes_with_map(
original: &str,
) -> std::result::Result<(String, Vec<usize>), AtomwriteError> {
let bytes = original.as_bytes();
let mut poll = CancelPoll::lines();
let mut out = String::with_capacity(original.len());
let mut map: Vec<usize> = Vec::with_capacity(original.len() + 1);
let mut i = 0usize;
while i < bytes.len() {
poll.tick()?;
if bytes[i] == b'\\' && i + 1 < bytes.len() {
let repl = match bytes[i + 1] {
b'n' => Some('\n'),
b't' => Some('\t'),
b'"' => Some('"'),
b'\\' => Some('\\'),
_ => None,
};
if let Some(ch) = repl {
let start = out.len();
out.push(ch);
for _ in start..out.len() {
map.push(i);
}
i += 2;
continue;
}
}
let Some(ch) = original.get(i..).and_then(|rest| rest.chars().next()) else {
break;
};
let ch_len = ch.len_utf8();
let start = out.len();
out.push(ch);
for _ in start..out.len() {
map.push(i);
}
i += ch_len;
}
map.push(original.len());
Ok((out, map))
}
fn match_escape_normalized(
content: &str,
pattern: &str,
) -> std::result::Result<Option<(usize, usize)>, AtomwriteError> {
match_escape_normalized_capped(
content,
pattern,
crate::constants::FUZZY_ESCAPE_MAP_MAX_CONTENT_BYTES,
)
}
fn match_escape_normalized_capped(
content: &str,
pattern: &str,
max_content_bytes: usize,
) -> std::result::Result<Option<(usize, usize)>, AtomwriteError> {
let (norm_pat, _) = normalize_escapes_with_map(pattern)?;
if norm_pat == pattern {
return Ok(None);
}
if content.len() > max_content_bytes {
tracing::debug!(
content_bytes = content.len(),
max_content_bytes,
"escape_normalized skipped: content above the offset-map cap"
);
return Ok(None);
}
let (norm_content, cmap) = normalize_escapes_with_map(content)?;
let Some(norm_pos) = norm_content.find(&norm_pat) else {
return Ok(None);
};
let norm_end = norm_pos + norm_pat.len();
if norm_end >= cmap.len() {
return Ok(None);
}
let orig_start = cmap[norm_pos];
let orig_end = cmap[norm_end];
if orig_start < orig_end && orig_end <= content.len() {
if !content.is_char_boundary(orig_start) || !content.is_char_boundary(orig_end) {
return Ok(None);
}
Ok(Some((orig_start, orig_end)))
} else {
Ok(None)
}
}
#[cfg(test)]
mod escape_map_tests {
use super::*;
#[test]
fn maps_expanded_newline_escape() {
let content = "a\\nb\\nc";
let pattern = "a\\nb";
let (s, e) = match_escape_normalized(content, pattern)
.expect("not cancelled")
.expect("match");
assert_eq!(&content[s..e], "a\\nb");
}
#[test]
fn skips_strategy_above_content_cap_without_building_the_map() {
let content = "a\\nb\\nc";
let pattern = "a\\nb";
let hit = match_escape_normalized_capped(content, pattern, content.len())
.expect("not cancelled");
assert!(hit.is_some(), "at the cap the strategy still runs");
let skipped = match_escape_normalized_capped(content, pattern, content.len() - 1)
.expect("skipping is not an error");
assert!(skipped.is_none(), "above the cap the strategy is skipped");
}
#[test]
fn escape_cap_constant_is_wired() {
let big = "x".repeat(crate::constants::FUZZY_ESCAPE_MAP_MAX_CONTENT_BYTES + 1) + "\\n";
assert!(
match_escape_normalized(&big, "q\\nr")
.expect("skipping is not an error")
.is_none()
);
}
#[test]
fn rejects_when_no_escapes_in_pattern() {
assert!(
match_escape_normalized("hello", "hello")
.expect("not cancelled")
.is_none()
);
}
}