tui_canvas/editor/
movement.rs1use crate::DataProvider;
4use crate::canvas::actions::movement::line::{line_end_position, line_start_position};
5use crate::canvas::actions::movement::word::{
6 find_last_big_word_start_in_field, find_last_word_start_in_field,
7};
8use crate::canvas::modes::AppMode;
9use crate::editor::EditorCore;
10
11impl<D: DataProvider> EditorCore<D> {
12 pub fn move_left(&mut self) -> anyhow::Result<()> {
14 self.break_undo_coalescing();
15
16 #[cfg(feature = "validation")]
17 let mut moved = false;
18 #[cfg(not(feature = "validation"))]
19 let moved = false;
20
21 #[cfg(feature = "validation")]
22 {
23 let field_index = self.ui_state.current_field;
24 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
25 if let Some(mask) = &cfg.display_mask {
26 let display_pos = mask.raw_pos_to_display_pos(self.ui_state.cursor_pos);
27 if let Some(prev_input) = mask.prev_input_position(display_pos) {
28 let raw_pos = mask.display_pos_to_raw_pos(prev_input);
29 let max_pos = self.current_text().chars().count();
30 self.set_cursor_raw(raw_pos.min(max_pos));
31 moved = true;
32 } else {
33 self.set_cursor_raw(0);
34 moved = true;
35 }
36 }
37 }
38 }
39
40 if !moved && self.ui_state.cursor_pos > 0 {
41 self.set_cursor_raw(self.ui_state.cursor_pos - 1);
42 }
43 Ok(())
44 }
45
46 pub fn move_right(&mut self) -> anyhow::Result<()> {
48 self.break_undo_coalescing();
49
50 #[cfg(feature = "validation")]
51 let mut moved = false;
52 #[cfg(not(feature = "validation"))]
53 let moved = false;
54
55 #[cfg(feature = "validation")]
56 {
57 let field_index = self.ui_state.current_field;
58 if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
59 if let Some(mask) = &cfg.display_mask {
60 let display_pos = mask.raw_pos_to_display_pos(self.ui_state.cursor_pos);
61 let next_display_pos = mask.next_input_position(display_pos);
62 let next_pos = mask.display_pos_to_raw_pos(next_display_pos);
63 let max_pos = self.current_text().chars().count();
64 self.set_cursor_raw(next_pos.min(max_pos));
65 moved = true;
66 }
67 }
68 }
69
70 if !moved {
71 let max_pos = self.current_text().chars().count();
72 if self.ui_state.cursor_pos < max_pos {
73 self.set_cursor_raw(self.ui_state.cursor_pos + 1);
74 }
75 }
76 Ok(())
77 }
78
79 pub fn move_line_start(&mut self) {
81 let new_pos = line_start_position();
82 self.set_cursor_raw(new_pos);
83 }
84
85 pub fn move_line_end(&mut self) {
87 let current_text = self.current_text();
88 let is_edit_mode = self.ui_state.current_mode == AppMode::Ins;
89
90 let new_pos = line_end_position(current_text, is_edit_mode);
91 self.set_cursor_raw(new_pos);
92 }
93
94 pub fn set_cursor_position(&mut self, position: usize) {
96 let current_text = self.current_text();
97 let char_len = current_text.chars().count();
98 self.set_cursor_for_mode(position, char_len);
99 }
100}
101
102impl<D: DataProvider> EditorCore<D> {
103 fn move_up_to_previous_field_and_set_last<F>(&mut self, mut position_for_field: F) -> bool
104 where
105 F: FnMut(&str) -> usize,
106 {
107 let current_field = self.ui_state.current_field;
108 if !self.move_up() || self.ui_state.current_field == current_field {
109 return false;
110 }
111
112 let new_text = self.current_text();
113 if !new_text.is_empty() {
114 let pos = position_for_field(new_text);
115 self.set_cursor_raw(pos);
116 }
117 true
118 }
119
120 fn move_down_to_next_field_and_set<F>(
121 &mut self,
122 set_zero_when_empty: bool,
123 mut position_for_field: F,
124 ) -> bool
125 where
126 F: FnMut(&str) -> usize,
127 {
128 if !self.move_down() {
129 return false;
130 }
131
132 let new_text = self.current_text();
133 if new_text.is_empty() {
134 if set_zero_when_empty {
135 self.set_cursor_raw(0);
136 }
137 } else {
138 let pos = position_for_field(new_text);
139 let char_len = new_text.chars().count();
140 self.set_cursor_for_mode(pos, char_len);
141 }
142 true
143 }
144
145 pub fn move_word_next(&mut self) {
147 use crate::canvas::actions::movement::word::find_next_word_start;
148 let current_text = self.current_text();
149
150 if current_text.is_empty() {
151 self.move_down_to_next_field_and_set(false, |new_text| {
152 if new_text.chars().next().is_some_and(|c| !c.is_whitespace()) {
153 0
154 } else {
155 find_next_word_start(new_text, 0)
156 }
157 });
158 return;
159 }
160
161 let current_pos = self.ui_state.cursor_pos;
162 let new_pos = find_next_word_start(current_text, current_pos);
163
164 if new_pos >= current_text.chars().count() {
165 self.move_down_to_next_field_and_set(true, |new_text| {
166 if new_text.chars().next().is_some_and(|c| !c.is_whitespace()) {
167 0
168 } else {
169 find_next_word_start(new_text, 0)
170 }
171 });
172 } else {
173 let char_len = current_text.chars().count();
174 self.set_cursor_for_mode(new_pos, char_len);
175 }
176 }
177
178 pub fn move_word_prev(&mut self) {
180 use crate::canvas::actions::movement::word::find_prev_word_start;
181 let current_text = self.current_text();
182
183 if current_text.is_empty() {
184 self.move_up_to_previous_field_and_set_last(find_last_word_start_in_field);
185 return;
186 }
187
188 let current_pos = self.ui_state.cursor_pos;
189
190 if current_pos == 0 {
191 self.move_up_to_previous_field_and_set_last(find_last_word_start_in_field);
192 return;
193 }
194
195 let new_pos = find_prev_word_start(current_text, current_pos);
196
197 if new_pos < current_pos {
198 self.set_cursor_raw(new_pos);
199 } else {
200 self.move_up_to_previous_field_and_set_last(find_last_word_start_in_field);
201 }
202 }
203
204 pub fn move_word_end(&mut self) {
206 use crate::canvas::actions::movement::word::find_word_end;
207 let current_text = self.current_text();
208 let char_len = current_text.chars().count();
209 let current_pos = self.ui_state.cursor_pos;
210
211 if current_text.is_empty() {
212 if self.move_down() {
213 self.set_cursor_raw(0);
214 }
215 return;
216 }
217
218 let mut target_pos = find_word_end(current_text, current_pos);
219
220 if target_pos <= current_pos && current_pos + 1 < char_len {
221 target_pos = find_word_end(current_text, current_pos + 1);
222 }
223
224 if target_pos > current_pos {
225 self.set_cursor_for_mode(target_pos, char_len);
226 } else {
227 if self.move_down() {
228 self.set_cursor_raw(0);
229
230 let next_text = self.current_text();
231 if !next_text.is_empty() {
232 let first_word_end = find_word_end(next_text, 0);
233 let next_char_len = next_text.chars().count();
234 self.set_cursor_for_mode(first_word_end, next_char_len);
235 }
236 }
237 }
238 }
239
240 pub fn move_word_end_prev(&mut self) {
242 use crate::canvas::actions::movement::word::{
243 find_last_word_end_in_field, find_prev_word_end,
244 };
245 let current_text = self.current_text();
246
247 if current_text.is_empty() {
248 self.move_up_to_previous_field_and_set_last(find_last_word_end_in_field);
249 return;
250 }
251
252 let current_pos = self.ui_state.cursor_pos;
253
254 if current_pos == 0 {
255 self.move_up_to_previous_field_and_set_last(find_last_word_end_in_field);
256 return;
257 }
258
259 let new_pos = find_prev_word_end(current_text, current_pos);
260
261 if new_pos == current_pos {
262 self.move_up_to_previous_field_and_set_last(find_last_word_end_in_field);
263 } else {
264 let char_len = current_text.chars().count();
265 self.set_cursor_for_mode(new_pos, char_len);
266 }
267 }
268
269 pub fn move_big_word_next(&mut self) {
271 use crate::canvas::actions::movement::word::find_next_big_word_start;
272 let current_text = self.current_text();
273
274 if current_text.is_empty() {
275 self.move_down_to_next_field_and_set(false, |new_text| {
276 if new_text.chars().next().is_some_and(|c| !c.is_whitespace()) {
277 0
278 } else {
279 find_next_big_word_start(new_text, 0)
280 }
281 });
282 return;
283 }
284
285 let current_pos = self.ui_state.cursor_pos;
286 let new_pos = find_next_big_word_start(current_text, current_pos);
287
288 if new_pos >= current_text.chars().count() {
289 self.move_down_to_next_field_and_set(true, |new_text| {
290 if new_text.chars().next().is_some_and(|c| !c.is_whitespace()) {
291 0
292 } else {
293 find_next_big_word_start(new_text, 0)
294 }
295 });
296 } else {
297 let char_len = current_text.chars().count();
298 self.set_cursor_for_mode(new_pos, char_len);
299 }
300 }
301
302 pub fn move_big_word_prev(&mut self) {
304 use crate::canvas::actions::movement::word::find_prev_big_word_start;
305 let current_text = self.current_text();
306
307 if current_text.is_empty() {
308 self.move_up_to_previous_field_and_set_last(find_last_big_word_start_in_field);
309 return;
310 }
311
312 let current_pos = self.ui_state.cursor_pos;
313
314 if current_pos == 0 {
315 self.move_up_to_previous_field_and_set_last(find_last_big_word_start_in_field);
316 return;
317 }
318
319 let new_pos = find_prev_big_word_start(current_text, current_pos);
320
321 if new_pos < current_pos {
322 self.set_cursor_raw(new_pos);
323 } else {
324 self.move_up_to_previous_field_and_set_last(find_last_big_word_start_in_field);
325 }
326 }
327
328 pub fn move_big_word_end(&mut self) {
330 use crate::canvas::actions::movement::word::find_big_word_end;
331 let current_text = self.current_text();
332
333 if current_text.is_empty() {
334 self.move_down_to_next_field_and_set(false, |new_text| find_big_word_end(new_text, 0));
335 return;
336 }
337
338 let current_pos = self.ui_state.cursor_pos;
339 let char_len = current_text.chars().count();
340 let new_pos = find_big_word_end(current_text, current_pos);
341
342 if new_pos == current_pos && current_pos + 1 < char_len {
343 let next_pos = find_big_word_end(current_text, current_pos + 1);
344 if next_pos < char_len {
345 self.set_cursor_for_mode(next_pos, char_len);
346 return;
347 }
348 }
349
350 if new_pos >= char_len.saturating_sub(1) {
351 self.move_down_to_next_field_and_set(false, |new_text| find_big_word_end(new_text, 0));
352 } else {
353 self.set_cursor_for_mode(new_pos, char_len);
354 }
355 }
356
357 pub fn move_big_word_end_prev(&mut self) {
359 use crate::canvas::actions::movement::word::{find_big_word_end, find_prev_big_word_end};
360
361 let current_text = self.current_text();
362
363 if current_text.is_empty() {
364 self.move_up_to_previous_field_and_set_last(|new_text| find_big_word_end(new_text, 0));
365 return;
366 }
367
368 let current_pos = self.ui_state.cursor_pos;
369 let new_pos = find_prev_big_word_end(current_text, current_pos);
370
371 if new_pos == current_pos {
372 self.move_up_to_previous_field_and_set_last(|new_text| find_big_word_end(new_text, 0));
373 } else {
374 let char_len = current_text.chars().count();
375 self.set_cursor_for_mode(new_pos, char_len);
376 }
377 }
378}