1use crate::model::buffer::Buffer;
8use crate::model::marker::MarkerList;
9use crate::view::overlay::{Overlay, OverlayFace, OverlayManager, OverlayNamespace};
10use crate::view::theme::Theme;
11use ratatui::style::Color;
12
13pub const DEFAULT_BRACKET_COLORS: [Color; 6] = [
15 Color::Rgb(255, 215, 0), Color::Rgb(218, 112, 214), Color::Rgb(50, 205, 50), Color::Rgb(30, 144, 255), Color::Rgb(255, 127, 80), Color::Rgb(147, 112, 219), ];
22
23pub fn bracket_highlight_namespace() -> OverlayNamespace {
25 OverlayNamespace::from_string("bracket-highlight".to_string())
26}
27
28pub fn bracket_colorization_namespace() -> OverlayNamespace {
30 OverlayNamespace::from_string("bracket-colorization".to_string())
31}
32
33const BRACKET_PAIRS: &[(char, char)] = &[('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
35
36pub(crate) const MAX_BRACKET_SEARCH_BYTES: usize = 1_000_000;
39
40const BRACKET_SCAN_CHUNK: usize = 16 * 1024;
42
43fn is_opening_bracket(ch: char) -> bool {
45 BRACKET_PAIRS.iter().any(|(open, _)| *open == ch)
46}
47
48fn is_closing_bracket(ch: char) -> bool {
50 BRACKET_PAIRS.iter().any(|(_, close)| *close == ch)
51}
52
53fn opening_for_closing(ch: char) -> Option<char> {
55 BRACKET_PAIRS
56 .iter()
57 .find_map(|(open, close)| if *close == ch { Some(*open) } else { None })
58}
59
60fn get_bracket_pair(ch: char) -> Option<(char, char, bool)> {
62 for &(open, close) in BRACKET_PAIRS {
63 if ch == open {
64 return Some((open, close, true)); }
66 if ch == close {
67 return Some((open, close, false)); }
69 }
70 None
71}
72
73pub struct BracketHighlightOverlay {
75 pub enabled: bool,
77 pub rainbow_enabled: bool,
79 pub rainbow_colors: [Color; 6],
81 pub match_color: Color,
83 last_cursor_pos: Option<usize>,
85}
86
87impl BracketHighlightOverlay {
88 pub fn new() -> Self {
90 Self {
91 enabled: true,
92 rainbow_enabled: true,
93 rainbow_colors: DEFAULT_BRACKET_COLORS,
94 match_color: Color::Rgb(255, 215, 0), last_cursor_pos: None,
96 }
97 }
98
99 pub fn update(
103 &mut self,
104 buffer: &Buffer,
105 overlays: &mut OverlayManager,
106 marker_list: &mut MarkerList,
107 theme: &Theme,
108 cursor_position: usize,
109 viewport_start: usize,
110 viewport_end: usize,
111 ) -> bool {
112 if !self.enabled && !self.rainbow_enabled {
113 return false;
114 }
115
116 let new_match_color = theme.bracket_match_fg;
117 let new_rainbow_colors = [
118 theme.bracket_rainbow_1,
119 theme.bracket_rainbow_2,
120 theme.bracket_rainbow_3,
121 theme.bracket_rainbow_4,
122 theme.bracket_rainbow_5,
123 theme.bracket_rainbow_6,
124 ];
125 let colors_changed =
126 self.match_color != new_match_color || self.rainbow_colors != new_rainbow_colors;
127 if colors_changed {
128 self.match_color = new_match_color;
129 self.rainbow_colors = new_rainbow_colors;
130 }
131
132 let mut updated = false;
133
134 if self.rainbow_enabled {
136 updated |= self.update_colorization(
137 buffer,
138 overlays,
139 marker_list,
140 viewport_start,
141 viewport_end,
142 );
143 } else {
144 updated |= self.clear_colorization(overlays, marker_list);
145 }
146
147 if !self.enabled {
149 return updated;
150 }
151
152 if self.last_cursor_pos == Some(cursor_position) && !colors_changed {
153 return updated;
154 }
155 self.last_cursor_pos = Some(cursor_position);
156 updated = true;
157
158 let ns = bracket_highlight_namespace();
160 overlays.clear_namespace(&ns, marker_list);
161
162 let buf_len = buffer.len();
164 if cursor_position >= buf_len {
165 return true;
166 }
167
168 let bytes = buffer.slice_bytes(cursor_position..cursor_position + 1);
169 if bytes.is_empty() {
170 return true;
171 }
172
173 let ch = bytes[0] as char;
174
175 let (opening, closing, forward) = match get_bracket_pair(ch) {
177 Some(pair) => pair,
178 None => return true, };
180
181 let depth = if self.rainbow_enabled {
183 self.calculate_nesting_depth(buffer, cursor_position, forward)
184 } else {
185 0
186 };
187
188 let matching_pos =
190 self.find_matching_bracket(buffer, cursor_position, opening, closing, forward);
191
192 let color = if self.rainbow_enabled {
194 self.rainbow_colors[depth % self.rainbow_colors.len()]
195 } else {
196 self.match_color
197 };
198
199 let cursor_face = OverlayFace::Foreground { color };
201 let cursor_overlay = Overlay::with_namespace(
202 marker_list,
203 cursor_position..cursor_position + 1,
204 cursor_face,
205 ns.clone(),
206 )
207 .with_priority_value(10);
208 overlays.add(cursor_overlay);
209
210 if let Some(match_pos) = matching_pos {
212 let match_face = OverlayFace::Foreground { color };
213 let match_overlay = Overlay::with_namespace(
214 marker_list,
215 match_pos..match_pos + 1,
216 match_face,
217 ns.clone(),
218 )
219 .with_priority_value(10);
220 overlays.add(match_overlay);
221 }
222
223 updated
224 }
225
226 fn calculate_nesting_depth(&self, buffer: &Buffer, position: usize, is_opening: bool) -> usize {
228 let scan_start = position.saturating_sub(MAX_BRACKET_SEARCH_BYTES);
231 let mut stack: Vec<char> = Vec::new();
232 let mut pos = scan_start;
233
234 while pos < position {
235 let chunk_end = (pos + BRACKET_SCAN_CHUNK).min(position);
236 let chunk = buffer.slice_bytes(pos..chunk_end);
237 for &b in &chunk {
238 let c = b as char;
239 if is_opening_bracket(c) {
240 stack.push(c);
241 } else if is_closing_bracket(c) {
242 if let Some(expected_open) = opening_for_closing(c) {
243 if stack.last() == Some(&expected_open) {
244 stack.pop();
245 }
246 }
247 }
248 }
249 pos = chunk_end;
250 }
251
252 if is_opening {
255 stack.len()
256 } else {
257 stack.len().saturating_sub(1)
258 }
259 }
260
261 fn find_matching_bracket(
263 &self,
264 buffer: &Buffer,
265 position: usize,
266 opening: char,
267 closing: char,
268 forward: bool,
269 ) -> Option<usize> {
270 let buffer_len = buffer.len();
271 let open = opening as u8;
272 let close = closing as u8;
273 let mut depth: i32 = 1;
274
275 if forward {
276 let search_limit = (position + 1 + MAX_BRACKET_SEARCH_BYTES).min(buffer_len);
277 let mut pos = position + 1;
278 while pos < search_limit {
279 let chunk_end = (pos + BRACKET_SCAN_CHUNK).min(search_limit);
280 let chunk = buffer.slice_bytes(pos..chunk_end);
281 for (i, &b) in chunk.iter().enumerate() {
282 if b == open {
283 depth += 1;
284 } else if b == close {
285 depth -= 1;
286 if depth == 0 {
287 return Some(pos + i);
288 }
289 }
290 }
291 pos = chunk_end;
292 }
293 } else {
294 let search_limit = position.saturating_sub(MAX_BRACKET_SEARCH_BYTES);
295 let mut pos = position;
296 while pos > search_limit {
297 let chunk_start = pos.saturating_sub(BRACKET_SCAN_CHUNK).max(search_limit);
298 let chunk = buffer.slice_bytes(chunk_start..pos);
299 for (i, &b) in chunk.iter().enumerate().rev() {
300 if b == close {
301 depth += 1;
302 } else if b == open {
303 depth -= 1;
304 if depth == 0 {
305 return Some(chunk_start + i);
306 }
307 }
308 }
309 pos = chunk_start;
310 }
311 }
312
313 None
314 }
315
316 pub fn clear(&mut self, overlays: &mut OverlayManager, marker_list: &mut MarkerList) {
318 let highlight_ns = bracket_highlight_namespace();
319 overlays.clear_namespace(&highlight_ns, marker_list);
320 let color_ns = bracket_colorization_namespace();
321 overlays.clear_namespace(&color_ns, marker_list);
322 self.last_cursor_pos = None;
323 }
324
325 pub fn invalidate(&mut self) {
327 self.last_cursor_pos = None;
328 }
329
330 fn clear_colorization(
331 &mut self,
332 overlays: &mut OverlayManager,
333 marker_list: &mut MarkerList,
334 ) -> bool {
335 let ns = bracket_colorization_namespace();
336 overlays.clear_namespace(&ns, marker_list);
337 true
338 }
339
340 fn update_colorization(
341 &mut self,
342 buffer: &Buffer,
343 overlays: &mut OverlayManager,
344 marker_list: &mut MarkerList,
345 viewport_start: usize,
346 viewport_end: usize,
347 ) -> bool {
348 if viewport_start >= viewport_end || buffer.len() == 0 {
349 return self.clear_colorization(overlays, marker_list);
350 }
351
352 let viewport_size = viewport_end.saturating_sub(viewport_start);
353 let scan_start = viewport_start.saturating_sub(viewport_size);
354 let scan_end = viewport_end.min(buffer.len());
355 if scan_start >= scan_end {
356 return self.clear_colorization(overlays, marker_list);
357 }
358
359 let bytes = buffer.slice_bytes(scan_start..scan_end);
360 if bytes.is_empty() {
361 return self.clear_colorization(overlays, marker_list);
362 }
363
364 let ns = bracket_colorization_namespace();
365 let mut stack: Vec<char> = Vec::new();
366 let mut new_overlays = Vec::new();
367
368 for (idx, byte) in bytes.iter().enumerate() {
369 let pos = scan_start + idx;
370 let c = *byte as char;
371
372 if is_opening_bracket(c) {
373 let depth = stack.len();
374 stack.push(c);
375 if pos >= viewport_start {
376 let color = self.rainbow_colors[depth % self.rainbow_colors.len()];
377 let face = OverlayFace::Foreground { color };
378 let overlay =
379 Overlay::with_namespace(marker_list, pos..pos + 1, face, ns.clone())
380 .with_priority_value(6);
381 new_overlays.push(overlay);
382 }
383 continue;
384 }
385
386 if is_closing_bracket(c) {
387 let depth = stack.len().saturating_sub(1);
388 if let Some(expected_open) = opening_for_closing(c) {
389 if stack.last() == Some(&expected_open) {
390 stack.pop();
391 }
392 }
393 if pos >= viewport_start {
394 let color = self.rainbow_colors[depth % self.rainbow_colors.len()];
395 let face = OverlayFace::Foreground { color };
396 let overlay =
397 Overlay::with_namespace(marker_list, pos..pos + 1, face, ns.clone())
398 .with_priority_value(6);
399 new_overlays.push(overlay);
400 }
401 }
402 }
403
404 overlays.replace_range_in_namespace(&ns, &(0..buffer.len()), new_overlays, marker_list);
405 true
406 }
407}
408
409impl Default for BracketHighlightOverlay {
410 fn default() -> Self {
411 Self::new()
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::model::buffer::Buffer;
419
420 #[test]
421 fn test_bracket_pair_detection() {
422 assert!(is_opening_bracket('('));
423 assert!(is_opening_bracket('['));
424 assert!(is_opening_bracket('{'));
425 assert!(!is_opening_bracket(')'));
426 assert!(!is_opening_bracket('a'));
427
428 assert!(is_closing_bracket(')'));
429 assert!(is_closing_bracket(']'));
430 assert!(is_closing_bracket('}'));
431 assert!(!is_closing_bracket('('));
432 assert!(!is_closing_bracket('a'));
433 }
434
435 #[test]
436 fn test_get_bracket_pair() {
437 assert_eq!(get_bracket_pair('('), Some(('(', ')', true)));
438 assert_eq!(get_bracket_pair(')'), Some(('(', ')', false)));
439 assert_eq!(get_bracket_pair('['), Some(('[', ']', true)));
440 assert_eq!(get_bracket_pair(']'), Some(('[', ']', false)));
441 assert_eq!(get_bracket_pair('a'), None);
442 }
443
444 #[test]
445 fn test_find_matching_bracket_forward() {
446 let buffer = Buffer::from_str_test("(hello)");
447 let overlay = BracketHighlightOverlay::new();
448
449 let result = overlay.find_matching_bracket(&buffer, 0, '(', ')', true);
450 assert_eq!(result, Some(6));
451 }
452
453 #[test]
454 fn test_find_matching_bracket_backward() {
455 let buffer = Buffer::from_str_test("(hello)");
456 let overlay = BracketHighlightOverlay::new();
457
458 let result = overlay.find_matching_bracket(&buffer, 6, '(', ')', false);
459 assert_eq!(result, Some(0));
460 }
461
462 #[test]
463 fn test_find_matching_bracket_nested() {
464 let buffer = Buffer::from_str_test("((inner))");
465 let overlay = BracketHighlightOverlay::new();
466
467 let result = overlay.find_matching_bracket(&buffer, 0, '(', ')', true);
469 assert_eq!(result, Some(8));
470
471 let result = overlay.find_matching_bracket(&buffer, 1, '(', ')', true);
473 assert_eq!(result, Some(7));
474 }
475
476 #[test]
477 fn test_nesting_depth() {
478 let buffer = Buffer::from_str_test("((()))");
479 let overlay = BracketHighlightOverlay::new();
480
481 assert_eq!(overlay.calculate_nesting_depth(&buffer, 0, true), 0);
483
484 assert_eq!(overlay.calculate_nesting_depth(&buffer, 1, true), 1);
486
487 assert_eq!(overlay.calculate_nesting_depth(&buffer, 2, true), 2);
489 }
490
491 #[test]
492 fn test_nesting_depth_mixed_types() {
493 let buffer = Buffer::from_str_test("({[]})");
494 let overlay = BracketHighlightOverlay::new();
495
496 assert_eq!(overlay.calculate_nesting_depth(&buffer, 0, true), 0);
497 assert_eq!(overlay.calculate_nesting_depth(&buffer, 1, true), 1);
498 assert_eq!(overlay.calculate_nesting_depth(&buffer, 2, true), 2);
499 assert_eq!(overlay.calculate_nesting_depth(&buffer, 3, false), 2);
500 assert_eq!(overlay.calculate_nesting_depth(&buffer, 4, false), 1);
501 assert_eq!(overlay.calculate_nesting_depth(&buffer, 5, false), 0);
502 }
503}