1#![allow(unused)]
2
3use crate::util;
4use crate::Options;
5use crate::CH_HEIGHT;
6use crate::CH_WIDTH;
7use crate::COMPONENT_NAME;
8use cell::Cell;
9use css_colors::rgba;
10use css_colors::Color;
11use css_colors::RGBA;
12use line::Line;
13use nalgebra::Point2;
14use range::Range;
15use sauron::html::attributes;
16use sauron::jss::jss_ns;
17use sauron::prelude::*;
18use sauron::Node;
19use std::iter::FromIterator;
20use ultron_syntaxes_themes::TextHighlighter;
21use ultron_syntaxes_themes::{Style, Theme};
22#[allow(unused)]
23use unicode_width::UnicodeWidthChar;
24
25mod cell;
26mod line;
27mod range;
28
29pub struct TextBuffer {
32 options: Options,
33 lines: Vec<Line>,
34 text_highlighter: TextHighlighter,
35 cursor: Point2<usize>,
36 #[allow(unused)]
37 selection_start: Option<Point2<usize>>,
38 #[allow(unused)]
39 selection_end: Option<Point2<usize>>,
40 focused_cell: Option<FocusCell>,
41}
42
43#[derive(Clone, Copy, Debug)]
44struct FocusCell {
45 line_index: usize,
46 range_index: usize,
47 cell_index: usize,
48 cell: Option<Cell>,
49}
50
51impl TextBuffer {
52 pub fn from_str(options: Options, content: &str) -> Self {
53 let mut text_highlighter = TextHighlighter::default();
54 if let Some(theme_name) = &options.theme_name {
55 log::trace!("Selecting theme: {}", theme_name);
56 text_highlighter.select_theme(theme_name);
57 }
58 let mut this = Self {
59 lines: Self::highlight_content(
60 content,
61 &text_highlighter,
62 &options.syntax_token,
63 ),
64 text_highlighter,
65 cursor: Point2::new(0, 0),
66 selection_start: None,
67 selection_end: None,
68 focused_cell: None,
69 options,
70 };
71
72 this.calculate_focused_cell();
73 this
74 }
75
76 pub fn clear(&mut self) {
77 self.lines.clear();
78 }
79
80 pub fn set_selection(&mut self, start: Point2<usize>, end: Point2<usize>) {
81 self.selection_start = Some(start);
82 self.selection_end = Some(end);
83 }
84
85 pub fn clear_selection(&mut self) {
87 self.selection_start = None;
88 self.selection_end = None;
89 }
90
91 pub fn select_all(&mut self) {
92 self.selection_start = Some(Point2::new(0, 0));
93 self.selection_end = Some(self.max_position());
94 }
95
96 pub fn normalize_selection(
98 &self,
99 ) -> Option<(Point2<usize>, Point2<usize>)> {
100 if let (Some(start), Some(end)) =
101 (self.selection_start, self.selection_end)
102 {
103 Some(util::normalize_points(start, end))
104 } else {
105 None
106 }
107 }
108
109 fn is_within_position(
110 &self,
111 (line_index, range_index, cell_index): (usize, usize, usize),
112 start: Point2<usize>,
113 end: Point2<usize>,
114 ) -> bool {
115 let x = self.lines[line_index]
116 .calc_range_cell_index_to_x(range_index, cell_index);
117 let y = line_index;
118
119 if self.options.use_block_mode {
120 x >= start.x && x <= end.x && y >= start.y && y <= end.y
121 } else {
122 if y > start.y && y < end.y {
123 true
124 } else {
125 let same_start_line = y == start.y;
126 let same_end_line = y == end.y;
127
128 if same_start_line && same_end_line {
129 x >= start.x && x <= end.x
130 } else if same_start_line {
131 x >= start.x
132 } else if same_end_line {
133 x <= end.x
134 } else {
135 false
136 }
137 }
138 }
139 }
140
141 pub(crate) fn in_selection(
143 &self,
144 line_index: usize,
145 range_index: usize,
146 cell_index: usize,
147 ) -> bool {
148 if let Some((start, end)) = self.normalize_selection() {
149 self.is_within_position(
150 (line_index, range_index, cell_index),
151 start,
152 end,
153 )
154 } else {
155 false
156 }
157 }
158
159 pub(crate) fn get_text(
162 &self,
163 start: Point2<usize>,
164 end: Point2<usize>,
165 ) -> String {
166 let (start, end) = util::normalize_points(start, end);
167 let mut buffer = TextBuffer::from_str(Options::default(), "");
168 for (line_index, line) in self.lines.iter().enumerate() {
169 let y = line_index;
170 for (range_index, range) in line.ranges.iter().enumerate() {
171 for (cell_index, cell) in range.cells.iter().enumerate() {
172 let x = line
173 .calc_range_cell_index_to_x(range_index, cell_index);
174 if self.is_within_position(
175 (line_index, range_index, cell_index),
176 start,
177 end,
178 ) {
179 if self.options.use_block_mode {
180 buffer.insert_char(
181 x - start.x,
182 y - start.y,
183 cell.ch,
184 );
185 } else {
186 let in_start_selection_line = y == start.y;
188 let new_x = if in_start_selection_line {
189 x - start.x
190 } else {
191 x
192 };
193 buffer.insert_char(new_x, y - start.y, cell.ch);
194 }
195 }
196 }
197 }
198 }
199 buffer.to_string()
200 }
201
202 pub(crate) fn cut_text(
204 &mut self,
205 start: Point2<usize>,
206 end: Point2<usize>,
207 ) -> String {
208 log::trace!("cutting from {} to {}", start, end);
209 let deleted_text = self.get_text(start, end);
210 if self.options.use_block_mode {
211 for line_index in start.y..=end.y {
212 println!("deleting cells in line: {}", line_index);
213 self.lines[line_index].delete_cells(start.x, end.x);
214 }
215 } else {
216 let is_one_line = start.y == end.y;
217 if is_one_line {
219 self.lines[start.y].delete_cells(start.x, end.x);
220 } else {
221 self.lines[end.y].delete_cells_from_start(end.x);
223 self.lines.drain(start.y + 1..end.y);
225 self.lines[start.y].delete_cells_to_end(start.x);
227 }
228 }
229 deleted_text
230 }
231
232 pub(crate) fn selected_text(&self) -> Option<String> {
233 if let (Some(start), Some(end)) =
234 (self.selection_start, self.selection_end)
235 {
236 Some(self.get_text(start, end))
237 } else {
238 None
239 }
240 }
241
242 pub(crate) fn cut_selected_text(&mut self) -> Option<String> {
243 if let (Some(start), Some(end)) =
244 (self.selection_start, self.selection_end)
245 {
246 Some(self.cut_text(start, end))
247 } else {
248 None
249 }
250 }
251
252 pub fn bounds(&self) -> Point2<i32> {
254 let total_lines = self.lines.len() as i32;
255 let max_column =
256 self.lines.iter().map(|line| line.width).max().unwrap_or(0) as i32;
257 Point2::new(max_column, total_lines)
258 }
259
260 pub fn set_options(&mut self, options: Options) {
261 self.options = options;
262 }
263
264 fn highlight_content(
265 content: &str,
266 text_highlighter: &TextHighlighter,
267 syntax_token: &str,
268 ) -> Vec<Line> {
269 let (mut line_highlighter, syntax_set) =
270 text_highlighter.get_line_highlighter(syntax_token);
271
272 content
273 .lines()
274 .map(|line| {
275 let line_str = String::from_iter(line.chars());
276 let style_range: Vec<(Style, &str)> =
277 line_highlighter.highlight(&line_str, syntax_set);
278
279 let ranges: Vec<Range> = style_range
280 .into_iter()
281 .map(|(style, range_str)| {
282 let cells =
283 range_str.chars().map(Cell::from_char).collect();
284 Range::from_cells(cells, style)
285 })
286 .collect();
287
288 Line::from_ranges(ranges)
289 })
290 .collect()
291 }
292
293 fn calculate_focused_cell(&mut self) {
294 self.focused_cell = self.find_focused_cell();
295 }
296
297 fn find_focused_cell(&self) -> Option<FocusCell> {
298 let line_index = self.cursor.y;
299 if let Some(line) = self.lines.get(line_index) {
300 if let Some((range_index, cell_index)) =
301 line.calc_range_cell_index_position(self.cursor.x)
302 {
303 if let Some(range) = line.ranges.get(range_index) {
304 return Some(FocusCell {
305 line_index,
306 range_index,
307 cell_index,
308 cell: range.cells.get(cell_index).cloned(),
309 });
310 }
311 }
312 }
313 return None;
314 }
315
316 fn is_focused_line(&self, line_index: usize) -> bool {
317 if let Some(focused_cell) = self.focused_cell {
318 focused_cell.matched_line(line_index)
319 } else {
320 false
321 }
322 }
323
324 fn is_focused_range(&self, line_index: usize, range_index: usize) -> bool {
325 if let Some(focused_cell) = self.focused_cell {
326 focused_cell.matched_range(line_index, range_index)
327 } else {
328 false
329 }
330 }
331
332 fn is_focused_cell(
333 &self,
334 line_index: usize,
335 range_index: usize,
336 cell_index: usize,
337 ) -> bool {
338 if let Some(focused_cell) = self.focused_cell {
339 focused_cell.matched(line_index, range_index, cell_index)
340 } else {
341 false
342 }
343 }
344
345 pub(crate) fn active_theme(&self) -> &Theme {
346 self.text_highlighter.active_theme()
347 }
348
349 pub(crate) fn gutter_background(&self) -> Option<RGBA> {
350 self.active_theme().settings.gutter.map(util::to_rgba)
351 }
352
353 pub(crate) fn gutter_foreground(&self) -> Option<RGBA> {
354 self.active_theme()
355 .settings
356 .gutter_foreground
357 .map(util::to_rgba)
358 }
359
360 pub(crate) fn theme_background(&self) -> Option<RGBA> {
361 self.active_theme().settings.background.map(util::to_rgba)
362 }
363
364 pub(crate) fn selection_background(&self) -> Option<RGBA> {
365 self.active_theme().settings.selection.map(util::to_rgba)
366 }
367
368 #[allow(unused)]
369 pub(crate) fn selection_foreground(&self) -> Option<RGBA> {
370 self.active_theme()
371 .settings
372 .selection_foreground
373 .map(util::to_rgba)
374 }
375
376 pub(crate) fn cursor_color(&self) -> Option<RGBA> {
377 self.active_theme().settings.caret.map(util::to_rgba)
378 }
379
380 fn numberline_wide(&self) -> usize {
382 if self.options.show_line_numbers {
383 self.lines.len().to_string().len()
384 } else {
385 0
386 }
387 }
388
389 pub(crate) fn numberline_padding_wide(&self) -> usize {
391 1
392 }
393
394 #[allow(unused)]
396 pub(crate) fn get_numberline_wide(&self) -> usize {
397 if self.options.show_line_numbers {
398 self.numberline_wide() + self.numberline_padding_wide()
399 } else {
400 0
401 }
402 }
403
404 pub fn view<MSG>(&self) -> Node<MSG> {
405 let class_ns = |class_names| {
406 attributes::class_namespaced(COMPONENT_NAME, class_names)
407 };
408 let class_number_wide =
409 format!("number_wide{}", self.numberline_wide());
410
411 let theme_background =
412 self.theme_background().unwrap_or(rgba(0, 0, 255, 1.0));
413
414 let code_attributes = [
415 class_ns("code"),
416 class_ns(&class_number_wide),
417 if self.options.use_background {
418 style! {background: theme_background.to_css()}
419 } else {
420 empty_attr()
421 },
422 ];
423
424 let rendered_lines = self
425 .lines
426 .iter()
427 .enumerate()
428 .map(|(line_index, line)| line.view_line(&self, line_index));
429
430 if self.options.use_for_ssg {
431 div(code_attributes, rendered_lines)
434 } else {
435 pre(
439 [class_ns("code_wrapper")],
440 [code(code_attributes, rendered_lines)],
441 )
442 }
443 }
444
445 pub fn style(&self) -> String {
446 let selection_bg = self
447 .selection_background()
448 .unwrap_or(rgba(100, 100, 100, 0.5));
449
450 let cursor_color = self.cursor_color().unwrap_or(rgba(255, 0, 0, 1.0));
451
452 jss_ns! {COMPONENT_NAME,
453 ".code_wrapper": {
454 margin: 0,
455 },
456
457 ".code": {
458 position: "relative",
459 font_size: px(14),
460 cursor: "text",
461 display: "block",
462 min_width: "max-content",
465 },
466
467 ".line_block": {
468 display: "block",
469 height: px(CH_HEIGHT),
470 },
471
472 ".number__line": {
474 display: "flex",
475 height: px(CH_HEIGHT),
476 },
477
478 ".number": {
480 flex: "none", text_align: "right",
482 background_color: "#002b36",
483 padding_right: px(CH_WIDTH * self.numberline_padding_wide() as u32),
484 height: px(CH_HEIGHT),
485 user_select: "none",
486 },
487 ".number_wide1 .number": {
488 width: px(1 * CH_WIDTH),
489 },
490 ".number_wide2 .number": {
492 width: px(2 * CH_WIDTH),
493 },
494 ".number_wide3 .number": {
496 width: px(3 * CH_WIDTH),
497 },
498 ".number_wide4 .number": {
500 width: px(4 * CH_WIDTH),
501 },
502 ".number_wide5 .number": {
504 width: px(5 * CH_WIDTH),
505 },
506
507 ".line": {
509 flex: "none", height: px(CH_HEIGHT),
511 overflow: "hidden",
512 display: "inline-block",
513 },
514
515 ".filler": {
516 width: percent(100),
517 },
518
519 ".line_focused": {
520 },
521
522 ".range": {
523 flex: "none",
524 height: px(CH_HEIGHT),
525 overflow: "hidden",
526 display: "inline-block",
527 },
528
529 ".line .ch": {
530 width: px(CH_WIDTH),
531 height: px(CH_HEIGHT),
532 font_stretch: "ultra-condensed",
533 font_variant_numeric: "slashed-zero",
534 font_kerning: "none",
535 font_size_adjust: "none",
536 font_optical_sizing: "none",
537 position: "relative",
538 overflow: "hidden",
539 align_items: "center",
540 line_height: 1,
541 display: "inline-block",
542 },
543
544 ".line .ch::selection": {
545 "background-color": selection_bg.to_css(),
546 },
547
548 ".ch.selected": {
549 background_color:selection_bg.to_css(),
550 },
551
552 ".virtual_cursor": {
553 position: "absolute",
554 width: px(CH_WIDTH),
555 height: px(CH_HEIGHT),
556 background_color: cursor_color.to_css(),
557 },
558
559 ".ch .cursor": {
560 position: "absolute",
561 left: 0,
562 width : px(CH_WIDTH),
563 height: px(CH_HEIGHT),
564 background_color: cursor_color.to_css(),
565 display: "inline",
566 animation: "cursor_blink-anim 1000ms step-end infinite",
567 },
568
569 ".ch.wide2 .cursor": {
570 width: px(2 * CH_WIDTH),
571 },
572 ".ch.wide3 .cursor": {
573 width: px(3 * CH_WIDTH),
574 },
575
576 ".thin_cursor .cursor": {
578 width: px(2),
579 },
580
581 ".block_cursor .cursor": {
582 width: px(CH_WIDTH),
583 },
584
585
586 ".line .ch.wide2": {
587 width: px(2 * CH_WIDTH),
588 font_size: px(13),
589 },
590
591 ".line .ch.wide3": {
592 width: px(3 * CH_WIDTH),
593 font_size: px(13),
594 },
595
596 "@keyframes cursor_blink-anim": {
597 "50%": {
598 background_color: "transparent",
599 border_color: "transparent",
600 },
601
602 "100%": {
603 background_color: cursor_color.to_css(),
604 border_color: "transparent",
605 },
606 },
607 }
608 }
609}
610
611impl TextBuffer {
615 pub(crate) fn total_lines(&self) -> usize {
617 self.lines.len()
618 }
619
620 pub(crate) fn is_in_virtual_position(&self) -> bool {
623 self.focused_cell.is_none()
624 }
625
626 pub(crate) fn rehighlight(&mut self) {
628 self.lines = Self::highlight_content(
629 &self.to_string(),
630 &self.text_highlighter,
631 &self.options.syntax_token,
632 );
633 }
634
635 #[allow(unused)]
637 pub(crate) fn line_width(&self, n: usize) -> Option<usize> {
638 self.lines.get(n).map(|l| l.width)
639 }
640
641 fn add_lines(&mut self, n: usize) {
643 for _i in 0..n {
644 self.lines.push(Line::default());
645 }
646 }
647
648 fn add_cell(&mut self, y: usize, n: usize) {
650 let ch = ' ';
651 for _i in 0..n {
652 self.lines[y].push_char(ch);
653 }
654 }
655
656 pub(crate) fn break_line(&mut self, x: usize, y: usize) {
658 if let Some(line) = self.lines.get_mut(y) {
659 let (range_index, col) = line
660 .calc_range_cell_index_position(x)
661 .unwrap_or(line.range_cell_next());
662 if let Some(range_bound) = line.ranges.get_mut(range_index) {
663 range_bound.recalc_width();
664 let mut other = range_bound.split_at(col);
665 other.recalc_width();
666 let mut rest =
667 line.ranges.drain(range_index + 1..).collect::<Vec<_>>();
668 rest.insert(0, other);
669 self.insert_line(y + 1, Line::from_ranges(rest));
670 } else {
671 self.insert_line(y, Line::default());
672 }
673 }
674 }
675
676 pub(crate) fn join_line(&mut self, x: usize, y: usize) {
677 if self.lines.get(y + 1).is_some() {
678 let next_line = self.lines.remove(y + 1);
679 self.lines[y].push_ranges(next_line.ranges);
680 }
681 }
682
683 fn assert_chars(&self, ch: char) {
684 assert!(
685 ch != '\n',
686 "line breaks should have been pre-processed before this point"
687 );
688 assert!(
689 ch != '\t',
690 "tabs should have been pre-processed before this point"
691 );
692 }
693
694 pub fn insert_char(&mut self, x: usize, y: usize, ch: char) {
696 self.assert_chars(ch);
697 self.ensure_cell_exist(x, y);
698
699 let (range_index, cell_index) = self.lines[y]
700 .calc_range_cell_index_position(x)
701 .unwrap_or(self.lines[y].range_cell_next());
702
703 self.lines[y].insert_char(range_index, cell_index, ch);
704 }
705
706 fn insert_line_text(&mut self, x: usize, y: usize, text: &str) {
707 let mut new_col = x;
708 for ch in text.chars() {
709 let width = ch.width().unwrap_or_else(|| {
710 panic!("must have a unicode width for {:?}", ch)
711 });
712 self.insert_char(new_col, y, ch);
713 new_col += width;
714 }
715 }
716
717 pub(crate) fn insert_text(&mut self, x: usize, y: usize, text: &str) {
718 self.ensure_cell_exist(x, y);
719 let lines: Vec<&str> = text.lines().collect();
720 if lines.len() == 1 {
721 self.insert_line_text(x, y, lines[0]);
722 } else {
723 dbg!(&self.lines);
724 let mut new_col = x;
725 let mut new_line = y;
726 for (line_index, line) in lines.iter().enumerate() {
727 println!("inserting {} at {},{}", line, new_col, new_line);
728 if line_index + 1 < lines.len() {
729 self.break_line(new_col, new_line);
730 }
731 self.insert_line_text(new_col, new_line, line);
732 new_col = 0;
733 new_line += 1;
734 }
735 }
736 }
737
738 pub fn replace_char(&mut self, x: usize, y: usize, ch: char) {
740 self.assert_chars(ch);
741 self.ensure_cell_exist(x + 1, y);
742
743 let (range_index, cell_index) = self.lines[y]
744 .calc_range_cell_index_position(x)
745 .expect("the range_index and cell_index must have existed at this point");
746 self.lines[y].replace_char(range_index, cell_index, ch);
747 }
748
749 pub(crate) fn delete_char(&mut self, x: usize, y: usize) -> Option<char> {
751 if let Some(line) = self.lines.get_mut(y) {
752 if let Some((range_index, col)) =
753 line.calc_range_cell_index_position(x)
754 {
755 if let Some(range) = line.ranges.get_mut(range_index) {
756 if range.cells.get(col).is_some() {
757 let cell = range.cells.remove(col);
758 return Some(cell.ch);
759 }
760 }
761 }
762 }
763 None
764 }
765
766 fn ensure_cell_exist(&mut self, x: usize, y: usize) {
767 self.ensure_line_exist(y);
768 let cell_gap = x.saturating_sub(self.lines[y].width);
769 if cell_gap > 0 {
770 self.add_cell(y, cell_gap);
771 }
772 }
773
774 fn ensure_line_exist(&mut self, y: usize) {
775 let line_gap = y.saturating_add(1).saturating_sub(self.total_lines());
776 if line_gap > 0 {
777 self.add_lines(line_gap);
778 }
779 }
780
781 fn insert_line(&mut self, line_index: usize, line: Line) {
782 self.ensure_line_exist(line_index.saturating_sub(1));
783 self.lines.insert(line_index, line);
784 }
785
786 fn focused_line(&self) -> Option<&Line> {
788 self.lines.get(self.cursor.y)
789 }
790
791 pub(crate) fn get_position(&self) -> Point2<usize> {
793 self.cursor
794 }
795
796 fn max_position(&self) -> Point2<usize> {
798 let last_y = self.lines.len().saturating_sub(1);
799
800 let last_x = if self.options.use_block_mode {
802 self.lines
803 .iter()
804 .map(|line| line.width.saturating_sub(1))
805 .max()
806 .unwrap_or(0)
807 } else {
808 if let Some(last_line) = self.lines.get(last_y) {
810 last_line.width.saturating_sub(1)
811 } else {
812 0
813 }
814 };
815 Point2::new(last_x, last_y)
816 }
817
818 fn calculate_offset(&self, text: &str) -> (usize, usize) {
819 let lines: Vec<&str> = text.lines().collect();
820 let cols = if let Some(last_line) = lines.last() {
821 last_line
822 .chars()
823 .map(|ch| ch.width().expect("chars must have a width"))
824 .sum()
825 } else {
826 0
827 };
828 (cols, lines.len().saturating_sub(1))
829 }
830}
831
832impl TextBuffer {
837 pub(crate) fn command_insert_char(&mut self, ch: char) {
838 self.insert_char(self.cursor.x, self.cursor.y, ch);
839 let width = ch.width().expect("must have a unicode width");
840 self.move_x(width);
841 }
842
843 pub(crate) fn command_insert_forward_char(&mut self, ch: char) {
845 self.insert_char(self.cursor.x, self.cursor.y, ch);
846 }
847
848 pub(crate) fn command_replace_char(&mut self, ch: char) {
849 self.replace_char(self.cursor.x, self.cursor.y, ch);
850 }
851
852 pub(crate) fn command_insert_text(&mut self, text: &str) {
853 use unicode_width::UnicodeWidthStr;
854 self.insert_text(self.cursor.x, self.cursor.y, text);
855 let (x, y) = self.calculate_offset(text);
856 self.move_y(y);
857 self.move_x(x);
858 self.calculate_focused_cell();
859 }
860 pub(crate) fn move_left(&mut self) {
861 self.cursor.x = self.cursor.x.saturating_sub(1);
862 self.calculate_focused_cell();
863 }
864 pub(crate) fn move_left_start(&mut self) {
865 self.cursor.x = 0;
866 self.calculate_focused_cell();
867 }
868
869 pub(crate) fn move_right(&mut self) {
870 self.cursor.x = self.cursor.x.saturating_add(1);
871 self.calculate_focused_cell();
872 }
873 pub(crate) fn move_right_end(&mut self) {
874 let line_width = self.focused_line().map(|l| l.width).unwrap_or(0);
875 self.cursor.x += line_width;
876 self.calculate_focused_cell();
877 }
878
879 pub(crate) fn move_x(&mut self, x: usize) {
880 self.cursor.x = self.cursor.x.saturating_add(x);
881 self.calculate_focused_cell();
882 }
883 pub(crate) fn move_y(&mut self, y: usize) {
884 self.cursor.y = self.cursor.y.saturating_add(y);
885 self.calculate_focused_cell();
886 }
887 pub(crate) fn move_up(&mut self) {
888 self.cursor.y = self.cursor.y.saturating_sub(1);
889 self.calculate_focused_cell();
890 }
891 pub(crate) fn move_down(&mut self) {
892 self.cursor.y = self.cursor.y.saturating_add(1);
893 self.calculate_focused_cell();
894 }
895 pub(crate) fn set_position(&mut self, x: usize, y: usize) {
896 self.cursor.x = x;
897 self.cursor.y = y;
898 self.calculate_focused_cell();
899 }
900 pub(crate) fn command_break_line(&mut self, x: usize, y: usize) {
901 self.break_line(x, y);
902 self.move_left_start();
903 self.move_down();
904 self.calculate_focused_cell();
905 }
906
907 pub(crate) fn command_join_line(&mut self, x: usize, y: usize) {
908 self.join_line(x, y);
909 self.set_position(x, y);
910 self.calculate_focused_cell();
911 }
912
913 pub(crate) fn command_delete_back(&mut self) -> Option<char> {
914 if self.cursor.x > 0 {
915 let c = self
916 .delete_char(self.cursor.x.saturating_sub(1), self.cursor.y);
917 self.move_left();
918 c
919 } else {
920 None
921 }
922 }
923 pub(crate) fn command_delete_forward(&mut self) -> Option<char> {
924 let c = self.delete_char(self.cursor.x, self.cursor.y);
925 self.calculate_focused_cell();
926 c
927 }
928 pub(crate) fn command_delete_selected_forward(&mut self) -> Option<String> {
929 if let Some((start, end)) = self.normalize_selection() {
930 let deleted_text = self.cut_text(start, end);
931 self.move_to(start);
932 Some(deleted_text)
933 } else {
934 None
935 }
936 }
937 pub(crate) fn move_to(&mut self, pos: Point2<usize>) {
938 self.cursor.x = pos.x;
939 self.cursor.y = pos.y;
940 self.calculate_focused_cell();
941 }
942}
943
944impl ToString for TextBuffer {
945 fn to_string(&self) -> String {
946 self.lines
947 .iter()
948 .map(|line| line.text())
949 .collect::<Vec<_>>()
950 .join("\n")
951 }
952}
953
954impl FocusCell {
955 fn matched(
956 &self,
957 line_index: usize,
958 range_index: usize,
959 cell_index: usize,
960 ) -> bool {
961 self.line_index == line_index
962 && self.range_index == range_index
963 && self.cell_index == cell_index
964 }
965 fn matched_line(&self, line_index: usize) -> bool {
966 self.line_index == line_index
967 }
968 fn matched_range(&self, line_index: usize, range_index: usize) -> bool {
969 self.line_index == line_index && self.range_index == range_index
970 }
971}
972
973#[cfg(test)]
974mod test {
975 use super::*;
976
977 #[test]
978 fn test_ensure_line_exist() {
979 let mut buffer = TextBuffer::from_str(Options::default(), "");
980 buffer.ensure_line_exist(10);
981 assert!(buffer.lines.get(10).is_some());
982 assert_eq!(buffer.total_lines(), 11);
983 }
984}