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)> {
match *chars.get(index)? {
open @ ('(' | '[' | '{') => {
forward_match(chars, index, open, closing(open)).map(|m| (index, m))
}
close @ (')' | ']' | '}') => {
backward_match(chars, index, opening(close), close).map(|m| (m, index))
}
_ => None,
}
}
const fn closing(open: char) -> char {
match open {
'(' => ')',
'[' => ']',
_ => '}',
}
}
const fn opening(close: char) -> char {
match close {
')' => '(',
']' => '[',
_ => '{',
}
}
#[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);
}
}