1use crate::completion::is_word_char;
4
5#[derive(Clone, Copy)]
7pub enum WordSwap {
8 Prev,
10 Next,
12}
13
14#[must_use]
22pub fn swap(
23 text: &str,
24 selection: (usize, usize),
25 direction: WordSwap,
26) -> Option<(String, (usize, usize))> {
27 let chars: Vec<char> = text.chars().collect();
28 let length = chars.len();
29 let caret = selection.0.min(selection.1).min(length);
30 let high = selection.0.max(selection.1).min(length);
31 let is_caret = caret == high;
32 let (start, end) = if is_caret {
33 current_word(&chars, caret)?
34 } else {
35 (caret, high)
36 };
37 let mut out = Vec::with_capacity(length);
38 let moved = match direction {
39 WordSwap::Next => {
40 let (next_start, next_end) = next_word(&chars, end)?;
41 out.extend_from_slice(chars.get(..start)?);
42 out.extend_from_slice(chars.get(next_start..next_end)?);
43 out.extend_from_slice(chars.get(end..next_start)?);
44 out.extend_from_slice(chars.get(start..end)?);
45 out.extend_from_slice(chars.get(next_end..)?);
46 if is_caret {
47 let moved_caret = caret + (next_end - end);
48 (moved_caret, moved_caret)
49 } else {
50 let shifted = start + (next_end - end);
51 (shifted, shifted + (end - start))
52 }
53 }
54 WordSwap::Prev => {
55 let (prev_start, prev_end) = prev_word(&chars, start)?;
56 out.extend_from_slice(chars.get(..prev_start)?);
57 out.extend_from_slice(chars.get(start..end)?);
58 out.extend_from_slice(chars.get(prev_end..start)?);
59 out.extend_from_slice(chars.get(prev_start..prev_end)?);
60 out.extend_from_slice(chars.get(end..)?);
61 if is_caret {
62 let moved_caret = prev_start + (caret - start);
63 (moved_caret, moved_caret)
64 } else {
65 (prev_start, prev_start + (end - start))
66 }
67 }
68 };
69 Some((out.into_iter().collect(), moved))
70}
71
72fn word_run<'a>(chars: impl Iterator<Item = &'a char>) -> usize {
73 chars
74 .take_while(|character| is_word_char(**character))
75 .count()
76}
77
78fn gap_run<'a>(chars: impl Iterator<Item = &'a char>) -> usize {
79 chars
80 .take_while(|character| !is_word_char(**character))
81 .count()
82}
83
84fn current_word(chars: &[char], caret: usize) -> Option<(usize, usize)> {
85 let start = caret - word_run(chars.iter().take(caret).rev());
86 let end = caret + word_run(chars.iter().skip(caret));
87 (start != end).then_some((start, end))
88}
89
90fn next_word(chars: &[char], from: usize) -> Option<(usize, usize)> {
91 let start = from + gap_run(chars.iter().skip(from));
92 if start >= chars.len() {
93 return None;
94 }
95 let end = start + word_run(chars.iter().skip(start));
96 Some((start, end))
97}
98
99fn prev_word(chars: &[char], before: usize) -> Option<(usize, usize)> {
100 let end = before - gap_run(chars.iter().take(before).rev());
101 if end == 0 {
102 return None;
103 }
104 Some((end - word_run(chars.iter().take(end).rev()), end))
105}