ultron_core/
text_edit.rs

1use crate::{util, TextBuffer};
2pub use action::Action;
3pub use history::Recorded;
4use nalgebra::Point2;
5use std::fmt;
6use unicode_width::UnicodeWidthChar;
7
8mod action;
9mod history;
10
11/// A struct with text_buffer, selection commands, and history recording for undo and redo editing
12#[derive(Default, Clone)]
13pub struct TextEdit {
14    text_buffer: TextBuffer,
15    /// for undo and redo
16    recorded: Recorded,
17    pub selection: Selection,
18}
19
20#[derive(Default, Clone)]
21pub struct Selection {
22    pub start: Option<Point2<i32>>,
23    pub end: Option<Point2<i32>>,
24}
25
26impl fmt::Debug for Selection {
27    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28        let start = if let Some(start) = self.start {
29            format!("({},{})", start.x, start.y)
30        } else {
31            "..".to_string()
32        };
33        let end = if let Some(end) = self.end {
34            format!("({},{})", end.x, end.y)
35        } else {
36            "..".to_string()
37        };
38
39        write!(f, "{start} -> {end}")
40    }
41}
42
43#[derive(Debug, Clone, Copy)]
44pub enum SelectionMode {
45    Linear,
46    Block,
47}
48
49impl Default for SelectionMode {
50    fn default() -> Self {
51        Self::Linear
52    }
53}
54
55impl TextEdit {
56    pub fn new_from_str(content: &str) -> Self {
57        let text_buffer = TextBuffer::new_from_str(content);
58        TextEdit {
59            text_buffer,
60            recorded: Recorded::new(),
61            selection: Selection::default(),
62        }
63    }
64
65    pub fn text_buffer(&self) -> &TextBuffer {
66        &self.text_buffer
67    }
68
69    pub fn set_selection(&mut self, start: Point2<i32>, end: Point2<i32>) {
70        self.selection.start = Some(start);
71        self.selection.end = Some(end);
72    }
73
74    pub fn set_selection_start(&mut self, start: Point2<i32>) {
75        self.selection.start = Some(start);
76    }
77
78    pub fn clear(&mut self) {
79        self.text_buffer.clear();
80        self.clear_selection();
81        self.recorded.clear();
82    }
83
84    pub fn set_selection_end(&mut self, end: Point2<i32>) {
85        self.selection.end = Some(end);
86    }
87
88    pub fn selection(&self) -> &Selection {
89        &self.selection
90    }
91
92    pub fn command_insert_char(&mut self, ch: char) {
93        let cursor = self.text_buffer.get_position();
94        self.text_buffer.command_insert_char(ch);
95        self.recorded.insert_char(cursor, ch);
96    }
97
98    pub fn get_char(&self, loc: Point2<usize>) -> Option<char> {
99        self.text_buffer.get_char(loc)
100    }
101
102    pub fn command_smart_replace_insert_char(&mut self, ch: char) {
103        let cursor = self.text_buffer.get_position();
104        let has_right_char = if let Some(ch) = self.get_char(Point2::new(cursor.x + 1, cursor.y)) {
105            !ch.is_whitespace()
106        } else {
107            false
108        };
109        if has_right_char {
110            self.command_insert_char(ch);
111        } else {
112            self.command_replace_char(ch);
113            self.command_move_right();
114        }
115    }
116
117    pub fn command_replace_char(&mut self, ch: char) {
118        let cursor = self.text_buffer.get_position();
119        if let Some(old_ch) = self.text_buffer.command_replace_char(ch) {
120            self.recorded.replace_char(cursor, old_ch, ch);
121        }
122    }
123
124    pub fn command_delete_back(&mut self) {
125        let ch = self.text_buffer.command_delete_back();
126        let cursor = self.text_buffer.get_position();
127        self.recorded.delete(cursor, ch);
128    }
129
130    pub fn command_delete_forward(&mut self) {
131        let ch = self.text_buffer.command_delete_forward();
132        let cursor = self.text_buffer.get_position();
133        self.recorded.delete(cursor, ch);
134    }
135
136    pub fn command_move_up(&mut self) {
137        self.text_buffer.move_up();
138    }
139
140    pub fn command_move_up_clamped(&mut self) {
141        self.text_buffer.move_up_clamped();
142    }
143
144    pub fn command_move_down(&mut self) {
145        self.text_buffer.move_down();
146    }
147
148    pub fn command_move_down_clamped(&mut self) {
149        self.text_buffer.move_down_clamped();
150    }
151
152    pub fn command_move_left(&mut self) {
153        self.text_buffer.move_left();
154    }
155
156    pub fn command_move_left_start(&mut self) {
157        self.text_buffer.move_left_start();
158    }
159
160    pub fn command_move_right(&mut self) {
161        self.text_buffer.move_right();
162    }
163
164    pub fn command_move_right_end(&mut self) {
165        self.text_buffer.move_right_end();
166    }
167
168    pub fn command_move_right_clamped(&mut self) {
169        self.text_buffer.move_right_clamped();
170    }
171
172    pub fn command_break_line(&mut self) {
173        let pos = self.text_buffer.get_position();
174        self.text_buffer.command_break_line(pos);
175        self.recorded.break_line(pos);
176    }
177
178    pub fn command_join_line(&mut self) {
179        let pos = self.text_buffer.get_position();
180        self.text_buffer.command_join_line(pos);
181        self.recorded.join_line(pos);
182    }
183
184    pub fn command_insert_text(&mut self, text: &str) {
185        self.text_buffer.command_insert_text(text);
186    }
187
188    pub fn command_set_position(&mut self, cursor: Point2<usize>) {
189        self.text_buffer.set_position(cursor);
190    }
191
192    pub fn command_set_position_clamped(&mut self, cursor: Point2<usize>) {
193        self.text_buffer.set_position_clamped(cursor);
194    }
195
196    pub fn command_set_selection(&mut self, start: Point2<i32>, end: Point2<i32>) {
197        self.set_selection(start, end)
198    }
199
200    pub fn command_select_all(&mut self) {
201        let start = Point2::new(0, 0);
202        let max = self.text_buffer.last_char_position();
203        let end = Point2::new(max.x as i32, max.y as i32);
204        self.set_selection(start, end);
205        log::info!(
206            "in text_edit: select_all selected text: {:?}",
207            self.selected_text_in_linear_mode()
208        );
209    }
210
211    pub fn command_select_all_block_mode(&mut self) {
212        let start = Point2::new(0, 0);
213        let max = self.text_buffer.max_position();
214        let end = Point2::new(max.x as i32, max.y as i32);
215        self.set_selection(start, end);
216    }
217
218    /// Make a history separator for the undo/redo
219    /// This is used for breaking undo action list
220    pub fn bump_history(&mut self) {
221        self.recorded.bump_history();
222    }
223
224    pub fn command_undo(&mut self) {
225        if let Some(location) = self.recorded.undo(&mut self.text_buffer) {
226            self.text_buffer.set_position(location);
227        }
228    }
229
230    pub fn command_redo(&mut self) {
231        if let Some(location) = self.recorded.redo(&mut self.text_buffer) {
232            self.text_buffer.set_position(location);
233        }
234    }
235
236    pub fn is_selected_in_linear_mode(&self, loc: Point2<i32>) -> bool {
237        match (self.selection.start, self.selection.end) {
238            (Some(start), Some(end)) => {
239                let (start, end) = util::reorder_top_down_left_right(start, end);
240                let only_one_line = start.y == end.y;
241                let in_first_line = loc.y == start.y;
242                let in_inner_line = loc.y > start.y && loc.y < end.y;
243                let in_last_line = loc.y == end.y;
244                if in_first_line {
245                    if only_one_line {
246                        loc.x >= start.x && loc.x <= end.x
247                    } else {
248                        loc.x >= start.x
249                    }
250                } else if in_inner_line {
251                    true
252                } else if in_last_line {
253                    if only_one_line {
254                        loc.x >= start.x && loc.x <= end.x
255                    } else {
256                        loc.x <= end.x
257                    }
258                } else {
259                    // outside line
260                    false
261                }
262            }
263            _ => false,
264        }
265    }
266
267    pub fn is_selected_in_block_mode(&self, loc: Point2<i32>) -> bool {
268        match (self.selection.start, self.selection.end) {
269            (Some(start), Some(end)) => {
270                let (start, end) = util::normalize_points(start, end);
271                loc.x >= start.x && loc.x <= end.x && loc.y >= start.y && loc.y <= end.y
272            }
273            _ => false,
274        }
275    }
276
277    /// clear the text selection
278    pub fn clear_selection(&mut self) {
279        self.selection.start = None;
280        self.selection.end = None;
281    }
282
283    pub fn selected_text_in_linear_mode(&self) -> Option<String> {
284        match self.selection_reorder_casted() {
285            Some((start, end)) => Some(self.text_buffer.get_text_in_linear_mode(start, end)),
286            _ => None,
287        }
288    }
289
290    pub fn selected_text_in_block_mode(&self) -> Option<String> {
291        match self.selection_normalized_casted() {
292            Some((start, end)) => Some(self.text_buffer.get_text_in_block_mode(start, end)),
293            _ => None,
294        }
295    }
296
297    pub fn cut_selected_text_in_linear_mode(&mut self) -> Option<String> {
298        match self.selection_reorder_casted() {
299            Some((start, end)) => {
300                let cut_text = self.text_buffer.cut_text_in_linear_mode(start, end);
301                if !cut_text.is_empty() {
302                    self.record_deleted_text(start, end, &cut_text);
303                }
304                Some(cut_text)
305            }
306            _ => None,
307        }
308    }
309
310    fn record_deleted_text(&mut self, start: Point2<usize>, _end: Point2<usize>, cut_text: &str) {
311        let lines = cut_text.lines();
312        for (y, line) in lines.enumerate() {
313            for (_x, ch) in line.chars().enumerate() {
314                let position = Point2::new(start.x, start.y + y);
315                self.recorded.delete(position, Some(ch));
316            }
317        }
318    }
319
320    /// return the selection points which is normalized and casted into usize
321    pub fn selection_normalized_casted(&self) -> Option<(Point2<usize>, Point2<usize>)> {
322        match (self.selection.start, self.selection.end) {
323            (Some(start), Some(end)) => {
324                let (start, end) = util::normalize_points(start, end);
325                let start = util::cast_point(start);
326                let end = util::cast_point(end);
327                let start = self.text_buffer.clamp_position(start);
328                let end = self.text_buffer.clamp_position(end);
329                Some((start, end))
330            }
331            _ => None,
332        }
333    }
334
335    pub fn selection_reorder_casted(&self) -> Option<(Point2<usize>, Point2<usize>)> {
336        match (self.selection.start, self.selection.end) {
337            (Some(start), Some(end)) => {
338                let (start, end) = util::reorder_top_down_left_right(start, end);
339                let start = util::cast_point(start);
340                let end = util::cast_point(end);
341                let start = self.text_buffer.clamp_position(start);
342                let end = self.text_buffer.clamp_position(end);
343                Some((start, end))
344            }
345            _ => None,
346        }
347    }
348
349    pub fn cut_selected_text_in_block_mode(&mut self) -> Option<String> {
350        match self.selection_normalized_casted() {
351            Some((start, end)) => {
352                let cut_text = self.text_buffer.cut_text_in_block_mode(start, end);
353                if !cut_text.is_empty() {
354                    self.record_deleted_text(start, end, &cut_text);
355                }
356                Some(cut_text)
357            }
358            _ => None,
359        }
360    }
361
362    pub fn paste_text_in_block_mode(&mut self, text_block: String) {
363        self.text_buffer.paste_text_in_block_mode(text_block);
364    }
365
366    /// paste the text block overlaying on the text content of the buffer
367    /// excluding the whitespace
368    pub fn command_merge_text(&mut self, text_block: String) {
369        for (line_index, line) in text_block.lines().enumerate() {
370            let mut width = 0;
371            let y = line_index;
372            for ch in line.chars() {
373                if ch != crate::BLANK_CH {
374                    let x = width;
375                    self.command_set_position(Point2::new(x, y));
376                    self.command_replace_char(ch);
377                }
378                width += ch.width().unwrap_or(0);
379            }
380        }
381    }
382
383    pub fn get_position(&self) -> Point2<usize> {
384        self.text_buffer.get_position()
385    }
386
387    pub fn max_position(&self) -> Point2<usize> {
388        self.text_buffer.max_position()
389    }
390
391    pub fn get_content(&self) -> String {
392        self.text_buffer.to_string()
393    }
394
395    pub fn total_lines(&self) -> usize {
396        self.text_buffer.total_lines()
397    }
398
399    pub fn lines(&self) -> Vec<String> {
400        self.text_buffer.lines()
401    }
402
403    /// return the number of characters to represent the line number of the last line of the text
404    /// buffer
405    pub fn numberline_wide(&self) -> usize {
406        self.text_buffer.numberline_wide()
407    }
408}