1use crate::config::CompletionType;
4use memchr::memchr;
5use std::borrow::Cow::{self, Borrowed, Owned};
6use std::cell::Cell;
7
8pub trait Highlighter {
15 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
21 let _ = pos;
22 Borrowed(line)
23 }
24 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
27 &'s self,
28 prompt: &'p str,
29 default: bool,
30 ) -> Cow<'b, str> {
31 let _ = default;
32 Borrowed(prompt)
33 }
34 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
37 Borrowed(hint)
38 }
39 fn highlight_candidate<'c>(
44 &self,
45 candidate: &'c str, completion: CompletionType,
47 ) -> Cow<'c, str> {
48 let _ = completion;
49 Borrowed(candidate)
50 }
51 fn highlight_char(&self, line: &str, pos: usize) -> bool {
57 let _ = (line, pos);
58 false
59 }
60}
61
62impl Highlighter for () {}
63
64impl<'r, H: ?Sized + Highlighter> Highlighter for &'r H {
65 fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
66 (**self).highlight(line, pos)
67 }
68
69 fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
70 &'s self,
71 prompt: &'p str,
72 default: bool,
73 ) -> Cow<'b, str> {
74 (**self).highlight_prompt(prompt, default)
75 }
76
77 fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
78 (**self).highlight_hint(hint)
79 }
80
81 fn highlight_candidate<'c>(
82 &self,
83 candidate: &'c str,
84 completion: CompletionType,
85 ) -> Cow<'c, str> {
86 (**self).highlight_candidate(candidate, completion)
87 }
88
89 fn highlight_char(&self, line: &str, pos: usize) -> bool {
90 (**self).highlight_char(line, pos)
91 }
92}
93
94const OPENS: &[u8; 3] = b"{[(";
95const CLOSES: &[u8; 3] = b"}])";
96
97#[derive(Default)]
101pub struct MatchingBracketHighlighter {
102 bracket: Cell<Option<(u8, usize)>>, }
104
105impl MatchingBracketHighlighter {
106 #[must_use]
108 pub fn new() -> Self {
109 Self {
110 bracket: Cell::new(None),
111 }
112 }
113}
114
115impl Highlighter for MatchingBracketHighlighter {
116 fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
117 if line.len() <= 1 {
118 return Borrowed(line);
119 }
120 if let Some((bracket, pos)) = self.bracket.get() {
122 if let Some((matching, idx)) = find_matching_bracket(line, pos, bracket) {
123 let mut copy = line.to_owned();
124 copy.replace_range(idx..=idx, &format!("\x1b[1;34m{}\x1b[0m", matching as char));
125 return Owned(copy);
126 }
127 }
128 Borrowed(line)
129 }
130
131 fn highlight_char(&self, line: &str, pos: usize) -> bool {
132 self.bracket.set(check_bracket(line, pos));
134 self.bracket.get().is_some()
135 }
136}
137
138fn find_matching_bracket(line: &str, pos: usize, bracket: u8) -> Option<(u8, usize)> {
139 let matching = matching_bracket(bracket);
140 let mut idx;
141 let mut unmatched = 1;
142 if is_open_bracket(bracket) {
143 idx = pos + 1;
145 let bytes = &line.as_bytes()[idx..];
146 for b in bytes {
147 if *b == matching {
148 unmatched -= 1;
149 if unmatched == 0 {
150 debug_assert_eq!(matching, line.as_bytes()[idx]);
151 return Some((matching, idx));
152 }
153 } else if *b == bracket {
154 unmatched += 1;
155 }
156 idx += 1;
157 }
158 debug_assert_eq!(idx, line.len());
159 } else {
160 idx = pos;
162 let bytes = &line.as_bytes()[..idx];
163 for b in bytes.iter().rev() {
164 if *b == matching {
165 unmatched -= 1;
166 if unmatched == 0 {
167 debug_assert_eq!(matching, line.as_bytes()[idx - 1]);
168 return Some((matching, idx - 1));
169 }
170 } else if *b == bracket {
171 unmatched += 1;
172 }
173 idx -= 1;
174 }
175 debug_assert_eq!(idx, 0);
176 }
177 None
178}
179
180fn check_bracket(line: &str, pos: usize) -> Option<(u8, usize)> {
182 if line.is_empty() {
183 return None;
184 }
185 let mut pos = pos;
186 if pos >= line.len() {
187 pos = line.len() - 1; let b = line.as_bytes()[pos]; if is_close_bracket(b) {
190 Some((b, pos))
191 } else {
192 None
193 }
194 } else {
195 let mut under_cursor = true;
196 loop {
197 let b = line.as_bytes()[pos];
198 if is_close_bracket(b) {
199 return if pos == 0 { None } else { Some((b, pos)) };
200 } else if is_open_bracket(b) {
201 return if pos + 1 == line.len() {
202 None
203 } else {
204 Some((b, pos))
205 };
206 } else if under_cursor && pos > 0 {
207 under_cursor = false;
208 pos -= 1; } else {
210 return None;
211 }
212 }
213 }
214}
215
216const fn matching_bracket(bracket: u8) -> u8 {
217 match bracket {
218 b'{' => b'}',
219 b'}' => b'{',
220 b'[' => b']',
221 b']' => b'[',
222 b'(' => b')',
223 b')' => b'(',
224 b => b,
225 }
226}
227fn is_open_bracket(bracket: u8) -> bool {
228 memchr(bracket, OPENS).is_some()
229}
230fn is_close_bracket(bracket: u8) -> bool {
231 memchr(bracket, CLOSES).is_some()
232}
233
234#[cfg(test)]
235mod tests {
236 #[test]
237 pub fn find_matching_bracket() {
238 use super::find_matching_bracket;
239 assert_eq!(find_matching_bracket("(...", 0, b'('), None);
240 assert_eq!(find_matching_bracket("...)", 3, b')'), None);
241
242 assert_eq!(find_matching_bracket("()..", 0, b'('), Some((b')', 1)));
243 assert_eq!(find_matching_bracket("(..)", 0, b'('), Some((b')', 3)));
244
245 assert_eq!(find_matching_bracket("..()", 3, b')'), Some((b'(', 2)));
246 assert_eq!(find_matching_bracket("(..)", 3, b')'), Some((b'(', 0)));
247
248 assert_eq!(find_matching_bracket("(())", 0, b'('), Some((b')', 3)));
249 assert_eq!(find_matching_bracket("(())", 3, b')'), Some((b'(', 0)));
250 }
251 #[test]
252 pub fn check_bracket() {
253 use super::check_bracket;
254 assert_eq!(check_bracket(")...", 0), None);
255 assert_eq!(check_bracket("(...", 2), None);
256 assert_eq!(check_bracket("...(", 3), None);
257 assert_eq!(check_bracket("...(", 4), None);
258 assert_eq!(check_bracket("..).", 4), None);
259
260 assert_eq!(check_bracket("(...", 0), Some((b'(', 0)));
261 assert_eq!(check_bracket("(...", 1), Some((b'(', 0)));
262 assert_eq!(check_bracket("...)", 3), Some((b')', 3)));
263 assert_eq!(check_bracket("...)", 4), Some((b')', 3)));
264 }
265 #[test]
266 pub fn matching_bracket() {
267 use super::matching_bracket;
268 assert_eq!(matching_bracket(b'('), b')');
269 assert_eq!(matching_bracket(b')'), b'(');
270 }
271
272 #[test]
273 pub fn is_open_bracket() {
274 use super::is_close_bracket;
275 use super::is_open_bracket;
276 assert!(is_open_bracket(b'('));
277 assert!(is_close_bracket(b')'));
278 }
279}