use super::rope_buffer::RopeBuffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextObject {
Word {
around: bool,
},
Pair {
open: char,
close: char,
around: bool,
},
Quote {
ch: char,
around: bool,
},
}
pub(super) fn object_for_char(c: char, around: bool) -> Option<TextObject> {
match c {
'w' => Some(TextObject::Word { around }),
'(' | ')' | 'b' => Some(TextObject::Pair {
open: '(',
close: ')',
around,
}),
'{' | '}' | 'B' => Some(TextObject::Pair {
open: '{',
close: '}',
around,
}),
'[' | ']' => Some(TextObject::Pair {
open: '[',
close: ']',
around,
}),
'<' | '>' => Some(TextObject::Pair {
open: '<',
close: '>',
around,
}),
'"' => Some(TextObject::Quote { ch: '"', around }),
'\'' => Some(TextObject::Quote { ch: '\'', around }),
'`' => Some(TextObject::Quote { ch: '`', around }),
_ => None,
}
}
pub(super) fn object_range_at_cursor(
ta: &RopeBuffer,
obj: TextObject,
) -> Option<(usize, usize, usize)> {
let (row, col) = super::cursor_tuple(ta);
let line = ta.row(row)?;
let chars: Vec<char> = line.chars().collect();
let (start, end) = object_range(&chars, col, obj)?;
Some((row, start, end))
}
pub(super) fn find_enclosing_pair(
chars: &[char],
col: usize,
open: char,
close: char,
) -> Option<(usize, usize)> {
let open_idx = if chars.get(col) == Some(&open) {
col
} else {
let mut depth = 0usize;
let mut found = None;
for i in (0..col).rev() {
if chars[i] == close {
depth += 1;
} else if chars[i] == open {
if depth == 0 {
found = Some(i);
break;
}
depth -= 1;
}
}
found?
};
let mut depth = 0usize;
let mut close_idx = None;
for (i, &ch) in chars.iter().enumerate().skip(open_idx + 1) {
if ch == open {
depth += 1;
} else if ch == close {
if depth == 0 {
close_idx = Some(i);
break;
}
depth -= 1;
}
}
Some((open_idx, close_idx?))
}
pub(super) fn object_range(chars: &[char], col: usize, obj: TextObject) -> Option<(usize, usize)> {
if chars.is_empty() || col >= chars.len() {
return None;
}
match obj {
TextObject::Word { around } => {
let is_word = |c: char| c.is_alphanumeric() || c == '_';
let mut s = col;
while s > 0 && is_word(chars[s - 1]) {
s -= 1;
}
let mut e = col;
while e < chars.len() && is_word(chars[e]) {
e += 1;
}
if around {
while e < chars.len() && chars[e].is_whitespace() {
e += 1;
}
}
Some((s, e))
}
TextObject::Quote { ch, around } => {
let positions: Vec<usize> = chars
.iter()
.enumerate()
.filter(|&(_, &c)| c == ch)
.map(|(i, _)| i)
.collect();
let pair = positions
.chunks(2)
.find(|p| p.len() == 2 && p[0] <= col && col <= p[1])?;
let (o, c) = (pair[0], pair[1]);
if around {
Some((o, c + 1))
} else {
Some((o + 1, c))
}
}
TextObject::Pair {
open,
close,
around,
} => {
let (o, c) = find_enclosing_pair(chars, col, open, close)?;
if around {
Some((o, c + 1))
} else {
Some((o + 1, c))
}
}
}
}