fn forward_match(chars: &[char], from: usize, open: char, close: char) -> Option<usize> {
let mut depth: usize = 1;
for (index, &character) in chars.iter().enumerate().skip(from + 1) {
if character == open {
depth += 1;
} else if character == close {
depth -= 1;
if depth == 0 {
return Some(index);
}
}
}
None
}
fn backward_match(chars: &[char], from: usize, open: char, close: char) -> Option<usize> {
let mut depth: usize = 1;
for (index, &character) in chars.iter().enumerate().take(from).rev() {
if character == close {
depth += 1;
} else if character == open {
depth -= 1;
if depth == 0 {
return Some(index);
}
}
}
None
}
#[must_use]
pub fn find_matching_bracket(text: &str, cursor: usize) -> Option<(usize, usize)> {
let chars: Vec<char> = text.chars().collect();
if let Some(result) = bracket_at(&chars, cursor) {
return Some(result);
}
bracket_at(&chars, cursor.checked_sub(1)?)
}
fn bracket_at(chars: &[char], index: usize) -> Option<(usize, usize)> {
let character = *chars.get(index)?;
if let Some(close) = closing(character) {
return forward_match(chars, index, character, close).map(|end| (index, end));
}
let open = opening(character)?;
backward_match(chars, index, open, character).map(|start| (start, index))
}
const fn closing(open: char) -> Option<char> {
match open {
'(' => Some(')'),
'[' => Some(']'),
'{' => Some('}'),
_ => None,
}
}
const fn opening(close: char) -> Option<char> {
match close {
')' => Some('('),
']' => Some('['),
'}' => Some('{'),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_pair() {
assert_eq!(find_matching_bracket("(hello)", 1), Some((0, 6)));
}
#[test]
fn cursor_before_close() {
assert_eq!(find_matching_bracket("(hello)", 6), Some((0, 6)));
}
#[test]
fn nested_brackets() {
assert_eq!(find_matching_bracket("a(b[c]d)e", 4), Some((3, 5)));
}
#[test]
fn unmatched() {
assert_eq!(find_matching_bracket("(hello", 1), None);
}
#[test]
fn mixed_types() {
assert_eq!(find_matching_bracket("([)]", 3), Some((1, 3)));
}
#[test]
fn no_bracket() {
assert_eq!(find_matching_bracket("hello", 2), None);
}
#[test]
fn cursor_at_open() {
assert_eq!(find_matching_bracket("(x)", 0), Some((0, 2)));
}
#[test]
fn cursor_at_close() {
assert_eq!(find_matching_bracket("(x)", 2), Some((0, 2)));
}
#[test]
fn empty_text() {
assert_eq!(find_matching_bracket("", 0), None);
}
}