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