use unicode_segmentation::UnicodeSegmentation;
use crate::buffer::TextBuffer;
use crate::position::Position;
const PAIRS: [(char, char); 3] = [('(', ')'), ('[', ']'), ('{', '}')];
fn close_for(open: char) -> Option<char> {
PAIRS.iter().find(|(o, _)| *o == open).map(|(_, c)| *c)
}
fn open_for(close: char) -> Option<char> {
PAIRS.iter().find(|(_, c)| *c == close).map(|(o, _)| *o)
}
fn char_at(buffer: &TextBuffer, at: Position) -> Option<char> {
buffer.with_line_str(at.line, |line| {
line.graphemes(true)
.nth(at.col)
.and_then(|g| g.chars().next())
})
}
pub fn match_at(
buffer: &TextBuffer,
at: Position,
max_lines: usize,
) -> Option<(Position, Position)> {
let mut probes = Vec::with_capacity(2);
probes.push(at);
if at.col > 0 {
probes.push(Position {
line: at.line,
col: at.col - 1,
});
}
for probe in probes {
let Some(ch) = char_at(buffer, probe) else {
continue;
};
if let Some(close) = close_for(ch) {
if let Some(found) = scan_forward(buffer, probe, ch, close, max_lines) {
return Some((probe, found));
}
} else if let Some(open) = open_for(ch)
&& let Some(found) = scan_backward(buffer, probe, open, ch, max_lines)
{
return Some((found, probe));
}
}
None
}
fn scan_line(
buffer: &TextBuffer,
line: usize,
range: impl Iterator<Item = usize>,
open: char,
close: char,
entering: char,
depth: &mut usize,
) -> Option<usize> {
buffer.with_line_str(line, |text| {
let graphemes: Vec<&str> = text.graphemes(true).collect();
for i in range {
let Some(c) = graphemes.get(i).and_then(|g| g.chars().next()) else {
continue;
};
if c == entering {
*depth += 1;
} else if (c == open || c == close) && c != entering {
*depth -= 1;
if *depth == 0 {
return Some(i);
}
}
}
None
})
}
fn scan_forward(
buffer: &TextBuffer,
from: Position,
open: char,
close: char,
max_lines: usize,
) -> Option<Position> {
let last = (from.line + max_lines).min(buffer.line_count().saturating_sub(1));
let mut depth = 1usize;
for line in from.line..=last {
let count = buffer.line_grapheme_count(line);
let start = if line == from.line { from.col + 1 } else { 0 };
if start >= count {
continue;
}
if let Some(col) = scan_line(buffer, line, start..count, open, close, open, &mut depth) {
return Some(Position { line, col });
}
}
None
}
fn scan_backward(
buffer: &TextBuffer,
from: Position,
open: char,
close: char,
max_lines: usize,
) -> Option<Position> {
let first = from.line.saturating_sub(max_lines);
let mut depth = 1usize;
for line in (first..=from.line).rev() {
let count = buffer.line_grapheme_count(line);
let end = if line == from.line { from.col } else { count };
if end == 0 {
continue;
}
if let Some(col) = scan_line(buffer, line, (0..end).rev(), open, close, close, &mut depth) {
return Some(Position { line, col });
}
}
None
}