revue/widget/input_widgets/textarea/
multi_cursor.rs1use super::cursor::{Cursor, CursorPos};
4
5impl TextArea {
6 pub fn add_cursor_at(&mut self, line: usize, col: usize) {
8 let line = line.min(self.lines.len().saturating_sub(1));
9 let col = col.min(self.line_len(line));
10 self.cursors.add_at(CursorPos::new(line, col));
11 }
12
13 pub fn add_cursor_above(&mut self) {
15 let primary = self.cursors.primary().pos;
16 if primary.line > 0 {
17 let new_line = primary.line - 1;
18 let new_col = primary.col.min(self.line_len(new_line));
19 self.cursors.add_at(CursorPos::new(new_line, new_col));
20 }
21 }
22
23 pub fn add_cursor_below(&mut self) {
25 let primary = self.cursors.primary().pos;
26 if primary.line + 1 < self.lines.len() {
27 let new_line = primary.line + 1;
28 let new_col = primary.col.min(self.line_len(new_line));
29 self.cursors.add_at(CursorPos::new(new_line, new_col));
30 }
31 }
32
33 pub fn clear_secondary_cursors(&mut self) {
35 self.cursors.clear_secondary();
36 }
37
38 fn get_word_at_cursor(&self) -> String {
40 let pos = self.cursors.primary().pos;
41 let Some(line) = self.lines.get(pos.line) else {
42 return String::new();
43 };
44 let chars: Vec<char> = line.chars().collect();
45
46 if chars.is_empty() || pos.col >= chars.len() {
47 return String::new();
48 }
49
50 let mut start = pos.col;
51 let mut end = pos.col;
52
53 while start > 0 && chars[start - 1].is_alphanumeric() {
55 start -= 1;
56 }
57
58 while end < chars.len() && chars[end].is_alphanumeric() {
60 end += 1;
61 }
62
63 chars[start..end].iter().collect()
64 }
65
66 fn get_word_or_selection(&self) -> String {
68 if let Some(text) = self.get_selection() {
70 return text;
71 }
72 self.get_word_at_cursor()
74 }
75
76 fn find_next_from(&self, text: &str, from: CursorPos) -> Option<CursorPos> {
78 if text.is_empty() {
79 return None;
80 }
81
82 let text_lower = text.to_lowercase();
83
84 for line_idx in from.line..self.lines.len() {
86 let Some(line) = self.lines.get(line_idx) else {
87 continue;
88 };
89 let line_lower = line.to_lowercase();
90
91 let start_col = if line_idx == from.line {
92 from.col + 1
93 } else {
94 0
95 };
96
97 if start_col < line.len() {
98 if let Some(pos) = line_lower[start_col..].find(&text_lower) {
99 return Some(CursorPos::new(line_idx, start_col + pos));
100 }
101 }
102 }
103
104 for line_idx in 0..=from.line.min(self.lines.len().saturating_sub(1)) {
106 let Some(line) = self.lines.get(line_idx) else {
107 continue;
108 };
109 let line_lower = line.to_lowercase();
110
111 let end_col = if line_idx == from.line {
112 from.col + 1
113 } else {
114 line.len()
115 };
116
117 if let Some(pos) = line_lower[..end_col].find(&text_lower) {
118 let found_pos = CursorPos::new(line_idx, pos);
119 if !self.cursors.iter().any(|c| c.pos == found_pos) {
121 return Some(found_pos);
122 }
123 }
124 }
125
126 None
127 }
128
129 pub fn select_next_occurrence(&mut self) {
131 let text = self.get_word_or_selection();
132 if text.is_empty() {
133 return;
134 }
135
136 let last_pos = self
138 .cursors
139 .iter()
140 .map(|c| c.pos)
141 .max()
142 .unwrap_or(CursorPos::new(0, 0));
143
144 if let Some(match_pos) = self.find_next_from(&text, last_pos) {
145 let end_col = match_pos.col + text.len();
146 let new_cursor =
147 Cursor::with_selection(CursorPos::new(match_pos.line, end_col), match_pos);
148 self.cursors.add(new_cursor);
149 }
150 }
151}
152
153use super::TextArea;