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}