1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use ropey::Rope;
/// A cursor motion primitive (word, line, paragraph).
pub enum Motion {
WordForward,
WordBackward,
WordEnd,
LineEnd,
LineStart,
ParagraphForward,
ParagraphBackward,
}
/// Compute the character range from `pos` for the given motion direction.
pub fn find_motion_range(rope: &Rope, pos: usize, motion: Motion) -> std::ops::Range<usize> {
match motion {
Motion::WordForward => {
let mut end = pos;
let len = rope.len_chars();
// Se estivermos em uma palavra, pula até o fim dela
if end < len && is_word_char(rope.char(end)) {
while end < len && is_word_char(rope.char(end)) {
end += 1;
}
}
// Pula espaços em branco (ou outros caracteres que não são de palavra)
while end < len && !is_word_char(rope.char(end)) {
end += 1;
}
pos..end
}
Motion::WordBackward => {
let mut start = pos;
// Se estivermos no início de uma palavra, ou em um espaço,
// primeiro voltamos para o final da palavra anterior.
if start > 0 && !is_word_char(rope.char(start - 1)) {
while start > 0 && !is_word_char(rope.char(start - 1)) {
start -= 1;
}
} else if start > 0 && is_word_char(rope.char(start - 1)) {
// Se já estivermos dentro de uma palavra, voltamos até o início dela.
// Mas se já estivermos no primeiro caractere da palavra, queremos a palavra anterior.
if start > 1 && !is_word_char(rope.char(start - 2)) {
start -= 1;
while start > 0 && !is_word_char(rope.char(start - 1)) {
start -= 1;
}
}
}
// Agora voltamos até o início da palavra atual
while start > 0 && is_word_char(rope.char(start - 1)) {
start -= 1;
}
start..pos
}
Motion::WordEnd => {
let mut end = pos;
let len = rope.len_chars();
// Se já estivermos no fim de uma palavra, ou em um espaço,
// primeiro avançamos para o início da próxima palavra.
if end < len && !is_word_char(rope.char(end)) {
while end < len && !is_word_char(rope.char(end)) {
end += 1;
}
} else if end + 1 < len && !is_word_char(rope.char(end + 1)) {
// Se estivermos no último caractere de uma palavra, pula para a próxima
end += 1;
while end < len && !is_word_char(rope.char(end)) {
end += 1;
}
}
// Agora avançamos até o final da palavra atual (ou da próxima, se pulamos)
if end < len {
while end + 1 < len && is_word_char(rope.char(end + 1)) {
end += 1;
}
}
pos..end
}
Motion::LineEnd => {
let line = rope.char_to_line(pos);
let end = rope.line_to_char(line + 1).saturating_sub(1);
pos..end
}
Motion::LineStart => {
let line = rope.char_to_line(pos);
let start = rope.line_to_char(line);
start..pos
}
_ => pos..pos,
}
}
fn is_word_char(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
#[cfg(test)]
mod tests {
use super::*;
use ropey::Rope;
#[test]
fn test_word_forward() {
let rope = Rope::from_str("hello world test");
// De 'h' (0) para início de 'world' (8)
assert_eq!(find_motion_range(&rope, 0, Motion::WordForward).end, 8);
// De meio da palavra para início da próxima
assert_eq!(find_motion_range(&rope, 2, Motion::WordForward).end, 8);
}
#[test]
fn test_word_backward() {
let rope = Rope::from_str("hello world test");
// De 'w' (8) para início de 'hello' (0)
assert_eq!(find_motion_range(&rope, 8, Motion::WordBackward).start, 0);
// De meio da palavra para início da mesma
assert_eq!(find_motion_range(&rope, 10, Motion::WordBackward).start, 8);
}
#[test]
fn test_word_end() {
let rope = Rope::from_str("hello world test");
// De 'h' (0) para fim de 'hello' (4)
assert_eq!(find_motion_range(&rope, 0, Motion::WordEnd).end, 4);
// De 'e' (1) para fim de 'hello' (4)
assert_eq!(find_motion_range(&rope, 1, Motion::WordEnd).end, 4);
// De 'o' (4) para fim de 'world' (12)
assert_eq!(find_motion_range(&rope, 4, Motion::WordEnd).end, 12);
// De espaço (5) para fim de 'world' (12)
assert_eq!(find_motion_range(&rope, 5, Motion::WordEnd).end, 12);
}
}