1fn forward_match(chars: &[char], from: usize, open: char, close: char) -> Option<usize> {
4 let mut depth: usize = 1;
5 for (index, &character) in chars.iter().enumerate().skip(from + 1) {
6 if character == open {
7 depth += 1;
8 } else if character == close {
9 depth -= 1;
10 if depth == 0 {
11 return Some(index);
12 }
13 }
14 }
15 None
16}
17
18fn backward_match(chars: &[char], from: usize, open: char, close: char) -> Option<usize> {
19 let mut depth: usize = 1;
20 for (index, &character) in chars.iter().enumerate().take(from).rev() {
21 if character == close {
22 depth += 1;
23 } else if character == open {
24 depth -= 1;
25 if depth == 0 {
26 return Some(index);
27 }
28 }
29 }
30 None
31}
32
33#[must_use]
37pub fn find_matching_bracket(text: &str, cursor: usize) -> Option<(usize, usize)> {
38 let chars: Vec<char> = text.chars().collect();
39
40 if let Some(result) = bracket_at(&chars, cursor) {
41 return Some(result);
42 }
43 bracket_at(&chars, cursor.checked_sub(1)?)
44}
45
46fn bracket_at(chars: &[char], index: usize) -> Option<(usize, usize)> {
47 let character = *chars.get(index)?;
48 if let Some(close) = closing(character) {
49 return forward_match(chars, index, character, close).map(|end| (index, end));
50 }
51 let open = opening(character)?;
52 backward_match(chars, index, open, character).map(|start| (start, index))
53}
54
55const fn closing(open: char) -> Option<char> {
56 match open {
57 '(' => Some(')'),
58 '[' => Some(']'),
59 '{' => Some('}'),
60 _ => None,
61 }
62}
63
64const fn opening(close: char) -> Option<char> {
65 match close {
66 ')' => Some('('),
67 ']' => Some('['),
68 '}' => Some('{'),
69 _ => None,
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn simple_pair() {
79 assert_eq!(find_matching_bracket("(hello)", 1), Some((0, 6)));
80 }
81
82 #[test]
83 fn cursor_before_close() {
84 assert_eq!(find_matching_bracket("(hello)", 6), Some((0, 6)));
85 }
86
87 #[test]
88 fn nested_brackets() {
89 assert_eq!(find_matching_bracket("a(b[c]d)e", 4), Some((3, 5)));
90 }
91
92 #[test]
93 fn unmatched() {
94 assert_eq!(find_matching_bracket("(hello", 1), None);
95 }
96
97 #[test]
98 fn mixed_types() {
99 assert_eq!(find_matching_bracket("([)]", 3), Some((1, 3)));
100 }
101
102 #[test]
103 fn no_bracket() {
104 assert_eq!(find_matching_bracket("hello", 2), None);
105 }
106
107 #[test]
108 fn cursor_at_open() {
109 assert_eq!(find_matching_bracket("(x)", 0), Some((0, 2)));
110 }
111
112 #[test]
113 fn cursor_at_close() {
114 assert_eq!(find_matching_bracket("(x)", 2), Some((0, 2)));
115 }
116
117 #[test]
118 fn empty_text() {
119 assert_eq!(find_matching_bracket("", 0), None);
120 }
121}