1use crate::buffer::{Buffer, Position};
4
5#[derive(Debug, Clone, Default)]
7pub struct MultiCursor {
8 pub extras: Vec<Position>,
9}
10
11impl MultiCursor {
12 pub fn new() -> Self {
13 Self::default()
14 }
15
16 pub fn clear(&mut self) {
17 self.extras.clear();
18 }
19
20 pub fn is_active(&self) -> bool {
21 !self.extras.is_empty()
22 }
23
24 pub fn count(&self, _primary: Position) -> usize {
25 1 + self.extras.len()
26 }
27
28 pub fn all(&self, primary: Position) -> Vec<Position> {
30 let mut v = vec![primary];
31 v.extend(self.extras.iter().copied());
32 v.sort_by(|a, b| a.row.cmp(&b.row).then(a.col.cmp(&b.col)));
33 v.dedup();
34 v
35 }
36
37 pub fn set_from_all(&mut self, mut all: Vec<Position>) {
39 all.sort_by(|a, b| a.row.cmp(&b.row).then(a.col.cmp(&b.col)));
40 all.dedup();
41 if all.is_empty() {
42 self.extras.clear();
43 return;
44 }
45 self.extras = all.into_iter().skip(1).collect();
47 }
48
49 pub fn add(&mut self, primary: Position, pos: Position) {
50 if pos == primary {
51 return;
52 }
53 if !self.extras.iter().any(|p| *p == pos) {
54 self.extras.push(pos);
55 self.extras
56 .sort_by(|a, b| a.row.cmp(&b.row).then(a.col.cmp(&b.col)));
57 }
58 }
59
60 pub fn remove_last(&mut self) -> bool {
61 self.extras.pop().is_some()
62 }
63
64 pub fn clamp_all(&mut self, buf: &Buffer) {
66 let max_row = buf.line_count().saturating_sub(1);
67 for p in &mut self.extras {
68 if p.row > max_row {
69 p.row = max_row;
70 }
71 let max_col = buf.line(p.row).chars().count();
72 if p.col > max_col {
73 p.col = max_col;
74 }
75 }
76 self.extras.retain(|p| p.row <= max_row);
77 }
78}
79
80pub fn word_at(buf: &Buffer, pos: Position) -> Option<(Position, Position, String)> {
82 let line = buf.line(pos.row);
83 let chars: Vec<char> = line.chars().collect();
84 if chars.is_empty() {
85 return None;
86 }
87 let col = pos.col.min(chars.len().saturating_sub(1).max(0));
88 if col >= chars.len() && chars.is_empty() {
89 return None;
90 }
91 let c = if col < chars.len() {
92 chars[col]
93 } else if col > 0 {
94 chars[col - 1]
95 } else {
96 return None;
97 };
98 if !(c.is_alphanumeric() || c == '_') {
99 return None;
100 }
101 let mut start = col.min(chars.len().saturating_sub(1));
102 let mut end = start;
103 while start > 0 && (chars[start - 1].is_alphanumeric() || chars[start - 1] == '_') {
104 start -= 1;
105 }
106 while end + 1 < chars.len() && (chars[end + 1].is_alphanumeric() || chars[end + 1] == '_') {
107 end += 1;
108 }
109 let word: String = chars[start..=end].iter().collect();
110 Some((
111 Position {
112 row: pos.row,
113 col: start,
114 },
115 Position {
116 row: pos.row,
117 col: end + 1,
118 },
119 word,
120 ))
121}
122
123pub fn find_next(buf: &Buffer, word: &str, from: Position) -> Option<Position> {
125 if word.is_empty() {
126 return None;
127 }
128 let n = buf.line_count();
129 for row in from.row..n {
131 let line = buf.line(row);
132 let start_col = if row == from.row { from.col } else { 0 };
133 let chars: Vec<char> = line.chars().collect();
134 if start_col >= chars.len() {
135 continue;
136 }
137 let s: String = chars[start_col..].iter().collect();
138 if let Some(rel) = s.find(word) {
139 let abs = start_col + rel;
141 if is_word_match(&chars, abs, word) {
142 return Some(Position {
143 row,
144 col: abs,
145 });
146 }
147 let mut search_from = abs + 1;
149 while search_from < chars.len() {
150 let rest: String = chars[search_from..].iter().collect();
151 if let Some(r2) = rest.find(word) {
152 let abs2 = search_from + r2;
153 if is_word_match(&chars, abs2, word) {
154 return Some(Position {
155 row,
156 col: abs2,
157 });
158 }
159 search_from = abs2 + 1;
160 } else {
161 break;
162 }
163 }
164 }
165 }
166 for row in 0..=from.row {
168 let line = buf.line(row);
169 let chars: Vec<char> = line.chars().collect();
170 let limit = if row == from.row {
171 from.col.min(chars.len())
172 } else {
173 chars.len()
174 };
175 let s: String = chars[..limit].iter().collect();
176 if let Some(abs) = s.find(word) {
177 if is_word_match(&chars, abs, word) {
178 return Some(Position { row, col: abs });
179 }
180 }
181 }
182 None
183}
184
185fn is_word_match(chars: &[char], start: usize, word: &str) -> bool {
186 let wchars: Vec<char> = word.chars().collect();
187 if start + wchars.len() > chars.len() {
188 return false;
189 }
190 if chars[start..start + wchars.len()] != wchars[..] {
191 return false;
192 }
193 let before_ok = start == 0
194 || !(chars[start - 1].is_alphanumeric() || chars[start - 1] == '_');
195 let after = start + wchars.len();
196 let after_ok = after >= chars.len()
197 || !(chars[after].is_alphanumeric() || chars[after] == '_');
198 before_ok && after_ok
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::buffer::Buffer;
205
206 #[test]
207 fn find_next_word() {
208 let buf = Buffer::from_string("foo bar foo\nfoo");
209 let p = find_next(&buf, "foo", Position { row: 0, col: 1 }).unwrap();
210 assert_eq!(p, Position { row: 0, col: 8 });
211 }
212}