use crate::model::buffer::Buffer;
use crate::model::marker::MarkerList;
use crate::view::overlay::{Overlay, OverlayFace, OverlayManager, OverlayNamespace};
use ratatui::style::Color;
pub const DEFAULT_BRACKET_COLORS: [Color; 6] = [
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), ];
pub fn bracket_highlight_namespace() -> OverlayNamespace {
OverlayNamespace::from_string("bracket-highlight".to_string())
}
const BRACKET_PAIRS: &[(char, char)] = &[('(', ')'), ('[', ']'), ('{', '}'), ('<', '>')];
#[cfg(test)]
fn is_opening_bracket(ch: char) -> bool {
BRACKET_PAIRS.iter().any(|(open, _)| *open == ch)
}
#[cfg(test)]
fn is_closing_bracket(ch: char) -> bool {
BRACKET_PAIRS.iter().any(|(_, close)| *close == ch)
}
fn get_bracket_pair(ch: char) -> Option<(char, char, bool)> {
for &(open, close) in BRACKET_PAIRS {
if ch == open {
return Some((open, close, true)); }
if ch == close {
return Some((open, close, false)); }
}
None
}
pub struct BracketHighlightOverlay {
pub enabled: bool,
pub rainbow_enabled: bool,
pub rainbow_colors: Vec<Color>,
pub match_color: Color,
last_cursor_pos: Option<usize>,
}
impl BracketHighlightOverlay {
pub fn new() -> Self {
Self {
enabled: true,
rainbow_enabled: true,
rainbow_colors: DEFAULT_BRACKET_COLORS.to_vec(),
match_color: Color::Rgb(255, 215, 0), last_cursor_pos: None,
}
}
pub fn update(
&mut self,
buffer: &Buffer,
overlays: &mut OverlayManager,
marker_list: &mut MarkerList,
cursor_position: usize,
) -> bool {
if !self.enabled {
return false;
}
if self.last_cursor_pos == Some(cursor_position) {
return false;
}
self.last_cursor_pos = Some(cursor_position);
let ns = bracket_highlight_namespace();
overlays.clear_namespace(&ns, marker_list);
let buf_len = buffer.len();
if cursor_position >= buf_len {
return true;
}
let bytes = buffer.slice_bytes(cursor_position..cursor_position + 1);
if bytes.is_empty() {
return true;
}
let ch = bytes[0] as char;
let (opening, closing, forward) = match get_bracket_pair(ch) {
Some(pair) => pair,
None => return true, };
let depth = if self.rainbow_enabled {
self.calculate_nesting_depth(buffer, cursor_position, opening, closing, forward)
} else {
0
};
let matching_pos =
self.find_matching_bracket(buffer, cursor_position, opening, closing, forward);
let color = if self.rainbow_enabled && !self.rainbow_colors.is_empty() {
self.rainbow_colors[depth % self.rainbow_colors.len()]
} else {
self.match_color
};
let cursor_face = OverlayFace::Foreground { color };
let cursor_overlay = Overlay::with_namespace(
marker_list,
cursor_position..cursor_position + 1,
cursor_face,
ns.clone(),
)
.with_priority_value(10);
overlays.add(cursor_overlay);
if let Some(match_pos) = matching_pos {
let match_face = OverlayFace::Foreground { color };
let match_overlay = Overlay::with_namespace(
marker_list,
match_pos..match_pos + 1,
match_face,
ns.clone(),
)
.with_priority_value(10);
overlays.add(match_overlay);
}
true
}
fn calculate_nesting_depth(
&self,
buffer: &Buffer,
position: usize,
opening: char,
closing: char,
is_opening: bool,
) -> usize {
let mut depth: usize = 0;
let mut pos = 0;
while pos < position {
let bytes = buffer.slice_bytes(pos..pos + 1);
if !bytes.is_empty() {
let c = bytes[0] as char;
if c == opening {
depth += 1;
} else if c == closing {
depth = depth.saturating_sub(1);
}
}
pos += 1;
}
if is_opening {
depth
} else {
depth.saturating_sub(1)
}
}
fn find_matching_bracket(
&self,
buffer: &Buffer,
position: usize,
opening: char,
closing: char,
forward: bool,
) -> Option<usize> {
let buffer_len = buffer.len();
let mut depth = 1;
if forward {
let mut search_pos = position + 1;
while search_pos < buffer_len && depth > 0 {
let b = buffer.slice_bytes(search_pos..search_pos + 1);
if !b.is_empty() {
let c = b[0] as char;
if c == opening {
depth += 1;
} else if c == closing {
depth -= 1;
if depth == 0 {
return Some(search_pos);
}
}
}
search_pos += 1;
}
} else {
let mut search_pos = position.saturating_sub(1);
loop {
let b = buffer.slice_bytes(search_pos..search_pos + 1);
if !b.is_empty() {
let c = b[0] as char;
if c == closing {
depth += 1;
} else if c == opening {
depth -= 1;
if depth == 0 {
return Some(search_pos);
}
}
}
if search_pos == 0 {
break;
}
search_pos -= 1;
}
}
None
}
pub fn clear(&mut self, overlays: &mut OverlayManager, marker_list: &mut MarkerList) {
let ns = bracket_highlight_namespace();
overlays.clear_namespace(&ns, marker_list);
self.last_cursor_pos = None;
}
pub fn invalidate(&mut self) {
self.last_cursor_pos = None;
}
}
impl Default for BracketHighlightOverlay {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::buffer::Buffer;
#[test]
fn test_bracket_pair_detection() {
assert!(is_opening_bracket('('));
assert!(is_opening_bracket('['));
assert!(is_opening_bracket('{'));
assert!(!is_opening_bracket(')'));
assert!(!is_opening_bracket('a'));
assert!(is_closing_bracket(')'));
assert!(is_closing_bracket(']'));
assert!(is_closing_bracket('}'));
assert!(!is_closing_bracket('('));
assert!(!is_closing_bracket('a'));
}
#[test]
fn test_get_bracket_pair() {
assert_eq!(get_bracket_pair('('), Some(('(', ')', true)));
assert_eq!(get_bracket_pair(')'), Some(('(', ')', false)));
assert_eq!(get_bracket_pair('['), Some(('[', ']', true)));
assert_eq!(get_bracket_pair(']'), Some(('[', ']', false)));
assert_eq!(get_bracket_pair('a'), None);
}
#[test]
fn test_find_matching_bracket_forward() {
let buffer = Buffer::from_str_test("(hello)");
let overlay = BracketHighlightOverlay::new();
let result = overlay.find_matching_bracket(&buffer, 0, '(', ')', true);
assert_eq!(result, Some(6));
}
#[test]
fn test_find_matching_bracket_backward() {
let buffer = Buffer::from_str_test("(hello)");
let overlay = BracketHighlightOverlay::new();
let result = overlay.find_matching_bracket(&buffer, 6, '(', ')', false);
assert_eq!(result, Some(0));
}
#[test]
fn test_find_matching_bracket_nested() {
let buffer = Buffer::from_str_test("((inner))");
let overlay = BracketHighlightOverlay::new();
let result = overlay.find_matching_bracket(&buffer, 0, '(', ')', true);
assert_eq!(result, Some(8));
let result = overlay.find_matching_bracket(&buffer, 1, '(', ')', true);
assert_eq!(result, Some(7));
}
#[test]
fn test_nesting_depth() {
let buffer = Buffer::from_str_test("((()))");
let overlay = BracketHighlightOverlay::new();
assert_eq!(
overlay.calculate_nesting_depth(&buffer, 0, '(', ')', true),
0
);
assert_eq!(
overlay.calculate_nesting_depth(&buffer, 1, '(', ')', true),
1
);
assert_eq!(
overlay.calculate_nesting_depth(&buffer, 2, '(', ')', true),
2
);
}
}