use strop_core::Buffer;
pub fn match_pair(buf: &Buffer, pos: usize) -> Option<usize> {
const PAIRS: &[(u8, u8)] = &[(b'(', b')'), (b'[', b']'), (b'{', b'}'), (b'<', b'>')];
let on = buf
.byte_at(pos)
.and_then(|b| PAIRS.iter().find(|(o, c)| *o == b || *c == b));
let (open, close, from) = match on {
Some(&(o, c)) => (o as char, c as char, pos),
None => {
let end = buf.line_end(buf.line_of(pos));
let mut i = pos;
loop {
if i >= end {
return None;
}
if let Some(&(o, c)) = PAIRS
.iter()
.find(|(o, c)| *o == buf.byte(i) || *c == buf.byte(i))
{
break (o as char, c as char, i);
}
i += 1;
}
}
};
let (o, c) = bracket_pair(buf, from, open, close)?;
let b = buf.byte_at(from)?;
if b == open as u8 {
Some(c)
} else {
Some(o)
}
}
pub(crate) fn bracket_pair(
buf: &Buffer,
pos: usize,
open: char,
close: char,
) -> Option<(usize, usize)> {
let (open, close) = (open as u32, close as u32);
let open = u8::try_from(open).expect("ascii delimiter");
let close = u8::try_from(close).expect("ascii delimiter");
let n = buf.len_bytes();
if n == 0 {
return None;
}
let mut o = pos.min(n - 1);
if buf.byte(o) == close && o > 0 {
o -= 1;
}
let mut depth = 0i32;
loop {
let b = buf.byte(o);
if b == close {
depth += 1;
} else if b == open {
if depth == 0 {
break;
}
depth -= 1;
}
if o == 0 {
return None;
}
o -= 1;
}
let open_pos = o;
let mut depth = 0i32;
let mut c = open_pos + 1;
loop {
if c >= n {
return None;
}
let b = buf.byte(c);
if b == open {
depth += 1;
} else if b == close {
if depth == 0 {
return Some((open_pos, c));
}
depth -= 1;
}
c += 1;
}
}
pub(crate) fn quote_pair(buf: &Buffer, pos: usize, q: char) -> Option<(usize, usize)> {
let q = u8::try_from(q as u32).expect("ascii delimiter");
let line = buf.line_of(pos);
let start = buf.line_start(line);
let end = buf.line_end(line);
let open = (start..=pos.min(end)).rev().find(|&i| buf.byte(i) == q);
let open = match open {
Some(o) => o,
None => (pos..end).find(|&i| buf.byte(i) == q)?, };
let close = (open + 1..end).find(|&i| buf.byte(i) == q)?;
if pos > close {
return None;
}
Some((open, close))
}
const BLANK: u8 = 0;
const WORD: u8 = 1;
const PUNCT: u8 = 2;
fn object_class(buf: &Buffer, pos: usize, big: bool) -> u8 {
let b = buf.byte(pos);
let character = if b.is_ascii() {
b as char
} else {
buf.text().char(buf.text().byte_to_char(pos))
};
if character.is_whitespace() {
return BLANK;
}
if big || character.is_alphanumeric() || character == '_' {
WORD
} else {
PUNCT
}
}
pub(crate) fn word_object(
buf: &Buffer,
pos: usize,
big: bool,
inner: bool,
) -> Option<(usize, usize)> {
let n = buf.len_bytes();
if pos >= n {
return None;
}
let class = object_class(buf, pos, big);
let mut s = pos;
while s > 0 && object_class(buf, s - 1, big) == class {
s -= 1;
}
let mut e = pos + 1;
while e < n && object_class(buf, e, big) == class {
e += 1;
}
if inner {
return Some((s, e));
}
if class == BLANK {
let mut end = e;
while end < n && object_class(buf, end, big) == BLANK {
end += 1;
}
if end >= n {
return None; }
let word = object_class(buf, end, big);
while end < n && object_class(buf, end, big) == word {
end += 1;
}
return Some((s, end));
}
let mut trail = e;
while trail < n && buf.byte(trail) != b'\n' && object_class(buf, trail, big) == BLANK {
trail += 1;
}
if trail > e {
return Some((s, trail));
}
if s > 0 && buf.byte(s - 1) != b'\n' && object_class(buf, s - 1, big) == BLANK {
let mut lead = s;
while lead > 0 && buf.byte(lead - 1) != b'\n' && object_class(buf, lead - 1, big) == BLANK {
lead -= 1;
}
if lead > 0 && buf.byte(lead - 1) != b'\n' {
return Some((lead, e));
}
}
Some((s, e))
}
pub fn word_run(buf: &Buffer, pos: usize) -> Option<(usize, usize)> {
let n = buf.len_bytes();
if pos >= n {
return None;
}
let class = object_class(buf, pos, false);
if class == BLANK {
return None;
}
let mut start = pos;
while start > 0 && object_class(buf, start - 1, false) == class {
start -= 1;
}
let mut end = pos + 1;
while end < n && object_class(buf, end, false) == class {
end += 1;
}
Some((start, end))
}
pub(crate) fn surround_pair(ch: char) -> Option<(char, char)> {
Some(match ch {
'b' | '(' | ')' => ('(', ')'),
'B' | '{' | '}' => ('{', '}'),
'r' | '[' | ']' => ('[', ']'),
'a' | '<' | '>' => ('<', '>'),
q @ ('"' | '\'' | '`') => (q, q),
_ => return None,
})
}