use super::{CharSearch, CharSearchDirection, CharSearchPlacement, clamp_to_boundary};
pub(super) fn previous_char_boundary(text: &str, index: usize) -> usize {
let index = clamp_to_boundary(text, index);
let previous = text[..index]
.char_indices()
.last()
.map_or(0, |(byte_index, _character)| byte_index);
if text[previous..index].starts_with('\n') {
index
} else {
previous
}
}
pub(super) fn next_char_boundary(text: &str, index: usize) -> usize {
let index = clamp_to_boundary(text, index);
let Some(character) = text[index..].chars().next() else {
return index;
};
if character == '\n' {
return index;
}
let next = index + character.len_utf8();
if next == text.len() || text[next..].starts_with('\n') {
index
} else {
next
}
}
pub(super) fn line_start(text: &str, index: usize) -> usize {
let index = clamp_to_boundary(text, index);
text[..index]
.rfind('\n')
.map_or(0, |newline_index| newline_index + '\n'.len_utf8())
}
pub(super) fn line_content_end(text: &str, index: usize) -> usize {
let index = clamp_to_boundary(text, index);
text[index..]
.find('\n')
.map_or(text.len(), |newline_offset| index + newline_offset)
}
pub(super) fn first_non_blank(text: &str, index: usize) -> usize {
let start = line_start(text, index);
let end = line_content_end(text, index);
text[start..end]
.char_indices()
.find_map(|(offset, character)| (!character.is_whitespace()).then_some(start + offset))
.unwrap_or(start)
}
pub(super) fn line_end(text: &str, index: usize) -> usize {
let start = line_start(text, index);
let end = line_content_end(text, index);
text[start..end]
.char_indices()
.last()
.map_or(start, |(offset, _character)| start + offset)
}
pub(super) fn screen_column(text: &str, index: usize, column: usize) -> usize {
let start = line_start(text, index);
let end = line_content_end(text, index);
let zero_based_column = column.saturating_sub(1);
text[start..end]
.char_indices()
.nth(zero_based_column)
.map_or_else(
|| line_end(text, index),
|(offset, _character)| start + offset,
)
}
pub(super) fn char_search(text: &str, index: usize, search: CharSearch) -> usize {
let index = clamp_to_boundary(text, index);
let line_start = text[..index]
.rfind('\n')
.map_or(0, |newline_index| newline_index + '\n'.len_utf8());
let line_end = text[index..]
.find('\n')
.map_or(text.len(), |newline_offset| index + newline_offset);
match search.direction {
CharSearchDirection::Forward => forward_char_search(text, index, line_end, search),
CharSearchDirection::Backward => backward_char_search(text, line_start, index, search),
}
}
fn forward_char_search(text: &str, index: usize, line_end: usize, search: CharSearch) -> usize {
let after_cursor = text[index..line_end]
.chars()
.next()
.map_or(index, |character| index + character.len_utf8());
text[after_cursor..line_end]
.char_indices()
.find_map(|(offset, character)| {
(character == search.target).then(|| {
let match_index = after_cursor + offset;
match search.placement {
CharSearchPlacement::OnMatch => match_index,
CharSearchPlacement::BeforeMatch => previous_char_boundary(text, match_index),
}
})
})
.unwrap_or(index)
}
fn backward_char_search(text: &str, line_start: usize, index: usize, search: CharSearch) -> usize {
text[line_start..index]
.char_indices()
.rev()
.find_map(|(offset, character)| {
(character == search.target).then(|| {
let match_index = line_start + offset;
match search.placement {
CharSearchPlacement::OnMatch => match_index,
CharSearchPlacement::BeforeMatch => next_char_boundary(text, match_index),
}
})
})
.unwrap_or(index)
}