use crate::completion::is_word_char;
#[derive(Clone, Copy)]
pub enum WordSwap {
Prev,
Next,
}
#[must_use]
pub fn swap(
text: &str,
selection: (usize, usize),
direction: WordSwap,
) -> Option<(String, (usize, usize))> {
let chars: Vec<char> = text.chars().collect();
let length = chars.len();
let caret = selection.0.min(selection.1).min(length);
let high = selection.0.max(selection.1).min(length);
let is_caret = caret == high;
let (start, end) = if is_caret {
current_word(&chars, caret)?
} else {
(caret, high)
};
let mut out = Vec::with_capacity(length);
let moved = match direction {
WordSwap::Next => {
let (next_start, next_end) = next_word(&chars, end)?;
out.extend_from_slice(&chars[..start]);
out.extend_from_slice(&chars[next_start..next_end]);
out.extend_from_slice(&chars[end..next_start]);
out.extend_from_slice(&chars[start..end]);
out.extend_from_slice(&chars[next_end..]);
if is_caret {
let moved_caret = caret + (next_end - end);
(moved_caret, moved_caret)
} else {
let shifted = start + (next_end - end);
(shifted, shifted + (end - start))
}
}
WordSwap::Prev => {
let (prev_start, prev_end) = prev_word(&chars, start)?;
out.extend_from_slice(&chars[..prev_start]);
out.extend_from_slice(&chars[start..end]);
out.extend_from_slice(&chars[prev_end..start]);
out.extend_from_slice(&chars[prev_start..prev_end]);
out.extend_from_slice(&chars[end..]);
if is_caret {
let moved_caret = prev_start + (caret - start);
(moved_caret, moved_caret)
} else {
(prev_start, prev_start + (end - start))
}
}
};
Some((out.into_iter().collect(), moved))
}
fn current_word(chars: &[char], caret: usize) -> Option<(usize, usize)> {
let mut start = caret;
while start > 0 && is_word_char(chars[start - 1]) {
start -= 1;
}
let mut end = caret;
while end < chars.len() && is_word_char(chars[end]) {
end += 1;
}
(start != end).then_some((start, end))
}
fn next_word(chars: &[char], from: usize) -> Option<(usize, usize)> {
let mut start = from;
while start < chars.len() && !is_word_char(chars[start]) {
start += 1;
}
if start == chars.len() {
return None;
}
let mut end = start;
while end < chars.len() && is_word_char(chars[end]) {
end += 1;
}
Some((start, end))
}
fn prev_word(chars: &[char], before: usize) -> Option<(usize, usize)> {
let mut end = before;
while end > 0 && !is_word_char(chars[end - 1]) {
end -= 1;
}
if end == 0 {
return None;
}
let mut start = end;
while start > 0 && is_word_char(chars[start - 1]) {
start -= 1;
}
Some((start, end))
}