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.get(..start)?);
out.extend_from_slice(chars.get(next_start..next_end)?);
out.extend_from_slice(chars.get(end..next_start)?);
out.extend_from_slice(chars.get(start..end)?);
out.extend_from_slice(chars.get(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.get(..prev_start)?);
out.extend_from_slice(chars.get(start..end)?);
out.extend_from_slice(chars.get(prev_end..start)?);
out.extend_from_slice(chars.get(prev_start..prev_end)?);
out.extend_from_slice(chars.get(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 word_run<'a>(chars: impl Iterator<Item = &'a char>) -> usize {
chars
.take_while(|character| is_word_char(**character))
.count()
}
fn gap_run<'a>(chars: impl Iterator<Item = &'a char>) -> usize {
chars
.take_while(|character| !is_word_char(**character))
.count()
}
fn current_word(chars: &[char], caret: usize) -> Option<(usize, usize)> {
let start = caret - word_run(chars.iter().take(caret).rev());
let end = caret + word_run(chars.iter().skip(caret));
(start != end).then_some((start, end))
}
fn next_word(chars: &[char], from: usize) -> Option<(usize, usize)> {
let start = from + gap_run(chars.iter().skip(from));
if start >= chars.len() {
return None;
}
let end = start + word_run(chars.iter().skip(start));
Some((start, end))
}
fn prev_word(chars: &[char], before: usize) -> Option<(usize, usize)> {
let end = before - gap_run(chars.iter().take(before).rev());
if end == 0 {
return None;
}
Some((end - word_run(chars.iter().take(end).rev()), end))
}