use super::{Buffer, Position};
#[must_use]
pub fn find_delimiter_pair(
buffer: &Buffer,
pos: Position,
open: char,
close: char,
) -> Option<(Position, Position)> {
if buffer.is_empty() {
return None;
}
let is_symmetric = open == close;
if is_symmetric {
find_symmetric_pair(buffer, pos, open)
} else {
find_asymmetric_pair(buffer, pos, open, close)
}
}
fn find_symmetric_pair(
buffer: &Buffer,
pos: Position,
quote: char,
) -> Option<(Position, Position)> {
let line = buffer.line(pos.line)?;
let chars: Vec<char> = line.chars().collect();
let quote_positions: Vec<usize> = chars
.iter()
.enumerate()
.filter(|&(_, c)| *c == quote)
.map(|(i, _)| i)
.collect();
if quote_positions.len() < 2 {
return None;
}
let mut i = 0;
while i + 1 < quote_positions.len() {
let open_idx = quote_positions[i];
let close_idx = quote_positions[i + 1];
if pos.column >= open_idx && pos.column <= close_idx {
return Some((Position::new(pos.line, open_idx), Position::new(pos.line, close_idx)));
}
i += 2;
}
None
}
fn find_asymmetric_pair(
buffer: &Buffer,
pos: Position,
open: char,
close: char,
) -> Option<(Position, Position)> {
let line = buffer.line(pos.line)?;
let chars: Vec<char> = line.chars().collect();
let current_char = chars.get(pos.column).copied();
match current_char {
Some(c) if c == open => {
let close_pos =
find_forward(buffer, Position::new(pos.line, pos.column + 1), open, close)?;
Some((pos, close_pos))
}
Some(c) if c == close => {
let open_pos = find_backward(
buffer,
Position::new(pos.line, pos.column.saturating_sub(1)),
open,
close,
)?;
Some((open_pos, pos))
}
_ => {
let open_pos = find_backward(buffer, pos, open, close)?;
let close_pos = find_forward(buffer, pos, open, close)?;
Some((open_pos, close_pos))
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn find_backward(buffer: &Buffer, pos: Position, open: char, close: char) -> Option<Position> {
let mut y = pos.line;
let mut x = pos.column;
let mut depth = 0i32;
loop {
if let Some(line) = buffer.line(y) {
let chars: Vec<char> = line.chars().collect();
let start = x.min(chars.len().saturating_sub(1));
for i in (0..=start).rev() {
if i >= chars.len() {
continue;
}
let c = chars[i];
if c == close {
depth += 1;
} else if c == open {
if depth == 0 {
return Some(Position::new(y, i));
}
depth -= 1;
}
}
}
if y == 0 {
break;
}
y -= 1;
x = buffer.line_len(y).unwrap_or(0);
}
None
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn find_forward(buffer: &Buffer, pos: Position, open: char, close: char) -> Option<Position> {
let mut y = pos.line;
let mut x = pos.column;
let mut depth = 0i32;
let line_count = buffer.line_count();
loop {
if let Some(line) = buffer.line(y) {
let chars: Vec<char> = line.chars().collect();
let start = if y == pos.line { x } else { 0 };
for (i, &c) in chars.iter().enumerate().skip(start) {
if c == open {
depth += 1;
} else if c == close {
if depth == 0 {
return Some(Position::new(y, i));
}
depth -= 1;
}
}
}
y += 1;
if y >= line_count {
break;
}
x = 0;
}
None
}
#[must_use]
pub fn find_matching_delimiter(buffer: &Buffer, pos: Position) -> Option<Position> {
let line = buffer.line(pos.line)?;
let chars: Vec<char> = line.chars().collect();
let c = *chars.get(pos.column)?;
match c {
'(' => find_forward(buffer, Position::new(pos.line, pos.column + 1), '(', ')'),
')' => {
find_backward(buffer, Position::new(pos.line, pos.column.saturating_sub(1)), '(', ')')
}
'[' => find_forward(buffer, Position::new(pos.line, pos.column + 1), '[', ']'),
']' => {
find_backward(buffer, Position::new(pos.line, pos.column.saturating_sub(1)), '[', ']')
}
'{' => find_forward(buffer, Position::new(pos.line, pos.column + 1), '{', '}'),
'}' => {
find_backward(buffer, Position::new(pos.line, pos.column.saturating_sub(1)), '{', '}')
}
'<' => find_forward(buffer, Position::new(pos.line, pos.column + 1), '<', '>'),
'>' => {
find_backward(buffer, Position::new(pos.line, pos.column.saturating_sub(1)), '<', '>')
}
_ => None,
}
}