rust_kanban/ui/text_box/
mod.rs

1// This implementation is a stripped down version inspired by https://github.com/rhysd/tui-textarea,
2// i have chosen this approach to allow me to independently make changes to the codebase without
3// having to worry about the original codebase, and use the latest possible ratatui version
4// without waiting for the original author as the original codebase is not actively maintained.
5
6use crate::{inputs::key::Key, util::spaces};
7use helper_enums::{CursorMove, TextBoxEditKind, TextBoxScroll, YankText};
8use helper_structs::{
9    CursorPos, TextBoxEdit, TextBoxHistory, TextBoxRenderer, TextBoxViewport, TextLineFormatter,
10};
11use ratatui::{
12    layout::Alignment,
13    style::{Modifier, Style},
14    text::Line,
15    widgets::{Block, Widget},
16};
17use std::cmp::Ordering;
18use unicode_width::UnicodeWidthChar;
19use utils::{find_word_end_forward, find_word_start_backward};
20
21pub mod helper_enums;
22pub mod helper_structs;
23pub mod utils;
24
25#[derive(Clone, Debug)]
26pub struct TextBox<'a> {
27    lines: Vec<String>,
28    block: Option<Block<'a>>,
29    style: Style,
30    cursor: (usize, usize),
31    tab_len: u8,
32    hard_tab_indent: bool,
33    history: TextBoxHistory,
34    cursor_line_style: Style,
35    line_number_style: Option<Style>,
36    pub(crate) viewport: TextBoxViewport,
37    cursor_style: Style,
38    yank: YankText,
39    alignment: Alignment,
40    pub single_line_mode: bool,
41    pub(crate) placeholder: String,
42    pub(crate) placeholder_style: Style,
43    mask: Option<char>,
44    selection_start: Option<(usize, usize)>,
45    select_style: Style,
46}
47
48impl<'a> TextBox<'a> {
49    pub fn new(mut lines: Vec<String>, single_line_mode: bool) -> Self {
50        if lines.is_empty() {
51            lines.push(String::new());
52        }
53
54        Self {
55            lines,
56            block: None,
57            style: Style::default(),
58            cursor: (0, 0),
59            tab_len: 2,
60            hard_tab_indent: false,
61            history: TextBoxHistory::new(9999),
62            cursor_line_style: Style::default(),
63            line_number_style: None,
64            viewport: TextBoxViewport::default(),
65            cursor_style: Style::default(),
66            yank: YankText::default(),
67            alignment: Alignment::Left,
68            single_line_mode,
69            placeholder: String::new(),
70            placeholder_style: Style::default(),
71            mask: None,
72            selection_start: None,
73            select_style: Style::default().add_modifier(Modifier::REVERSED),
74        }
75    }
76
77    pub fn from_list_of_strings(lines: Vec<String>, single_line_mode: bool) -> Self {
78        Self::new(lines, single_line_mode)
79    }
80
81    pub fn from_list_of_str(lines: Vec<&'a str>, single_line_mode: bool) -> Self {
82        Self::new(
83            lines.into_iter().map(|s| s.to_string()).collect(),
84            single_line_mode,
85        )
86    }
87
88    pub fn from_string_with_newline_sep(s: String, single_line_mode: bool) -> Self {
89        Self::new(
90            s.split('\n').map(|s| s.to_string()).collect(),
91            single_line_mode,
92        )
93    }
94
95    pub fn reset(&mut self) {
96        let single_line_mode = self.single_line_mode;
97        *self = Self::new(vec![String::new()], single_line_mode);
98    }
99
100    pub fn get_joined_lines(&self) -> String {
101        self.lines.join("\n")
102    }
103
104    pub fn get_num_lines(&self) -> usize {
105        self.lines.len()
106    }
107
108    pub fn set_placeholder_text(&mut self, placeholder: impl Into<String>) {
109        self.placeholder = placeholder.into();
110    }
111
112    pub fn set_mask_char(&mut self, mask: char) {
113        self.mask = Some(mask);
114    }
115
116    pub fn clear_mask_char(&mut self) {
117        self.mask = None;
118    }
119
120    pub fn disable_cursor(&mut self) {
121        self.cursor_style = Style::default();
122    }
123
124    pub fn enable_cursor(&mut self, cursor_style: Style) {
125        self.cursor_style = cursor_style;
126    }
127
128    // TODO: Add keybindings to README
129    pub fn input(&mut self, input: Key) -> bool {
130        match input {
131            Key::Ctrl('m') | Key::Char('\n' | '\r') | Key::Enter => {
132                if self.single_line_mode {
133                    return false;
134                }
135                self.insert_newline();
136                true
137            }
138            Key::Char(c) => {
139                self.insert_char(c);
140                true
141            }
142            Key::Tab => {
143                if self.single_line_mode {
144                    return false;
145                }
146                self.insert_tab()
147            }
148            Key::Ctrl('h') | Key::Backspace => self.delete_char(),
149            Key::Ctrl('d') | Key::Delete => self.delete_next_char(),
150            Key::Ctrl('k') => self.delete_line_by_end(),
151            Key::Ctrl('j') => self.delete_line_by_head(),
152            Key::Ctrl('w') | Key::Alt('h') | Key::AltBackspace => self.delete_word(),
153            Key::AltDelete | Key::Alt('d') => self.delete_next_word(),
154            Key::Ctrl('n') | Key::Down => {
155                if self.single_line_mode {
156                    return false;
157                }
158                self.move_cursor(CursorMove::Down);
159                false
160            }
161            Key::ShiftDown => {
162                if self.single_line_mode {
163                    return false;
164                }
165                self.move_cursor_with_shift(CursorMove::Down, true);
166                false
167            }
168            Key::Ctrl('p') | Key::Up => {
169                if self.single_line_mode {
170                    return false;
171                }
172                self.move_cursor(CursorMove::Up);
173                false
174            }
175            Key::ShiftUp => {
176                if self.single_line_mode {
177                    return false;
178                }
179                self.move_cursor_with_shift(CursorMove::Up, true);
180                false
181            }
182            Key::Ctrl('f') | Key::Right => {
183                self.move_cursor(CursorMove::Forward);
184                false
185            }
186            Key::ShiftRight => {
187                self.move_cursor_with_shift(CursorMove::Forward, true);
188                false
189            }
190            Key::Ctrl('b') | Key::Left => {
191                self.move_cursor(CursorMove::Back);
192                false
193            }
194            Key::Ctrl('a') => {
195                self.select_all();
196                false
197            }
198            Key::ShiftLeft => {
199                self.move_cursor_with_shift(CursorMove::Back, true);
200                false
201            }
202            Key::Home | Key::CtrlAlt('b') | Key::CtrlAltLeft => {
203                self.move_cursor(CursorMove::Head);
204                false
205            }
206            Key::ShiftHome | Key::CtrlAltShift('b') | Key::CtrlAltShiftLeft => {
207                self.move_cursor_with_shift(CursorMove::Head, true);
208                false
209            }
210            Key::Ctrl('e') | Key::End | Key::CtrlAltRight | Key::CtrlAlt('f') => {
211                self.move_cursor(CursorMove::End);
212                false
213            }
214            Key::CtrlShift('e')
215            | Key::ShiftEnd
216            | Key::CtrlAltShiftRight
217            | Key::CtrlAltShift('f') => {
218                self.move_cursor_with_shift(CursorMove::End, true);
219                false
220            }
221            Key::Alt('<') | Key::CtrlAltUp | Key::CtrlAlt('p') => {
222                if self.single_line_mode {
223                    return false;
224                }
225                self.move_cursor(CursorMove::Top);
226                false
227            }
228            Key::AltShift('<') | Key::CtrlAltShiftUp | Key::CtrlAltShift('p') => {
229                if self.single_line_mode {
230                    return false;
231                }
232                self.move_cursor_with_shift(CursorMove::Top, true);
233                false
234            }
235            Key::Alt('>') | Key::CtrlAltDown | Key::CtrlAlt('n') => {
236                if self.single_line_mode {
237                    return false;
238                }
239                self.move_cursor(CursorMove::Bottom);
240                false
241            }
242            Key::AltShift('>') | Key::CtrlAltShiftDown | Key::CtrlAltShift('n') => {
243                if self.single_line_mode {
244                    return false;
245                }
246                self.move_cursor_with_shift(CursorMove::Bottom, true);
247                false
248            }
249            Key::Alt('f') | Key::CtrlRight => {
250                self.move_cursor(CursorMove::WordForward);
251                false
252            }
253            Key::AltShift('f') | Key::CtrlShiftRight => {
254                self.move_cursor_with_shift(CursorMove::WordForward, true);
255                false
256            }
257            Key::Alt('b') | Key::CtrlLeft => {
258                self.move_cursor(CursorMove::WordBack);
259                false
260            }
261            Key::AltShift('b') | Key::CtrlShiftLeft => {
262                self.move_cursor_with_shift(CursorMove::WordBack, true);
263                false
264            }
265            Key::Alt(']') | Key::Alt('n') | Key::CtrlDown => {
266                self.move_cursor(CursorMove::ParagraphForward);
267                false
268            }
269            Key::AltShift(']') | Key::AltShift('n') | Key::CtrlShiftDown => {
270                self.move_cursor_with_shift(CursorMove::ParagraphForward, true);
271                false
272            }
273            Key::Alt('[') | Key::Alt('p') | Key::CtrlUp => {
274                self.move_cursor(CursorMove::ParagraphBack);
275                false
276            }
277            Key::AltShift('[') | Key::AltShift('p') | Key::CtrlShiftUp => {
278                self.move_cursor_with_shift(CursorMove::ParagraphBack, true);
279                false
280            }
281            Key::Ctrl('z') => self.undo(),
282            Key::Ctrl('y') => self.redo(),
283            Key::Ctrl('c') => {
284                self.copy();
285                false
286            }
287            Key::Ctrl('x') => self.cut(),
288            Key::Ctrl('v') => self.paste(),
289            Key::PageDown => {
290                if self.single_line_mode {
291                    return false;
292                }
293                self.scroll(TextBoxScroll::PageDown);
294                false
295            }
296            Key::ShiftPageDown => {
297                if self.single_line_mode {
298                    return false;
299                }
300                self.scroll_with_shift(TextBoxScroll::PageDown, true);
301                false
302            }
303            Key::PageUp => {
304                if self.single_line_mode {
305                    return false;
306                }
307                self.scroll(TextBoxScroll::PageUp);
308                false
309            }
310            Key::ShiftPageUp => {
311                if self.single_line_mode {
312                    return false;
313                }
314                self.scroll_with_shift(TextBoxScroll::PageUp, true);
315                false
316            }
317            _ => false,
318        }
319    }
320
321    pub fn input_without_shortcuts(&mut self, input: Key) -> bool {
322        match input {
323            Key::Char(c) => {
324                self.insert_char(c);
325                true
326            }
327            Key::Tab => self.insert_tab(),
328            Key::Backspace => self.delete_char(),
329            Key::Delete => self.delete_next_char(),
330            Key::Enter => {
331                self.insert_newline();
332                true
333            }
334            _ => false,
335        }
336    }
337
338    pub fn set_selection_style(&mut self, style: Style) {
339        self.select_style = style;
340    }
341
342    fn line_offset(&self, row: usize, col: usize) -> usize {
343        let line = self
344            .lines
345            .get(row)
346            .unwrap_or(&self.lines[self.lines.len() - 1]);
347        line.char_indices()
348            .nth(col)
349            .map(|(i, _)| i)
350            .unwrap_or(line.len())
351    }
352
353    pub fn copy(&mut self) {
354        if let Some((start, end)) = self.take_selection_range() {
355            if start.row == end.row {
356                self.yank = self.lines[start.row][start.offset..end.offset]
357                    .to_string()
358                    .into();
359                return;
360            }
361            let mut chunk = vec![self.lines[start.row][start.offset..].to_string()];
362            chunk.extend(self.lines[start.row + 1..end.row].iter().cloned());
363            chunk.push(self.lines[end.row][..end.offset].to_string());
364            self.yank = YankText::Chunk(chunk);
365        }
366    }
367
368    pub fn cut(&mut self) -> bool {
369        self.delete_selection(true)
370    }
371
372    pub fn paste(&mut self) -> bool {
373        self.delete_selection(false);
374        match self.yank.clone() {
375            YankText::Piece(s) => self.insert_piece(s),
376            YankText::Chunk(c) => self.insert_chunk(c),
377        }
378    }
379
380    fn selection_range(&self) -> Option<(CursorPos, CursorPos)> {
381        let (sr, sc) = self.selection_start?;
382        let (er, ec) = self.cursor;
383        let (so, eo) = (self.line_offset(sr, sc), self.line_offset(er, ec));
384        let s = CursorPos::new(sr, sc, so);
385        let e = CursorPos::new(er, ec, eo);
386        match (sr, so).cmp(&(er, eo)) {
387            Ordering::Less => Some((s, e)),
388            Ordering::Equal => None,
389            Ordering::Greater => Some((e, s)),
390        }
391    }
392
393    fn take_selection_range(&mut self) -> Option<(CursorPos, CursorPos)> {
394        let range = self.selection_range();
395        self.cancel_selection();
396        range
397    }
398
399    fn delete_range(&mut self, start: CursorPos, end: CursorPos, should_yank: bool) {
400        self.cursor = (start.row, start.col);
401
402        if start.row == end.row {
403            let removed = self.lines[start.row]
404                .drain(start.offset..end.offset)
405                .as_str()
406                .to_string();
407            if should_yank {
408                self.yank = removed.clone().into();
409            }
410            self.push_history(TextBoxEditKind::DeleteStr(removed), end, start.offset);
411            return;
412        }
413
414        let mut deleted = vec![self.lines[start.row]
415            .drain(start.offset..)
416            .as_str()
417            .to_string()];
418        deleted.extend(self.lines.drain(start.row + 1..end.row));
419        if start.row + 1 < self.lines.len() {
420            let mut last_line = self.lines.remove(start.row + 1);
421            self.lines[start.row].push_str(&last_line[end.offset..]);
422            last_line.truncate(end.offset);
423            deleted.push(last_line);
424        }
425
426        if should_yank {
427            self.yank = YankText::Chunk(deleted.clone());
428        }
429
430        let edit = if deleted.len() == 1 {
431            TextBoxEditKind::DeleteStr(deleted.remove(0))
432        } else {
433            TextBoxEditKind::DeleteChunk(deleted)
434        };
435        self.push_history(edit, end, start.offset);
436    }
437
438    fn delete_selection(&mut self, should_yank: bool) -> bool {
439        if let Some((s, e)) = self.take_selection_range() {
440            self.delete_range(s, e, should_yank);
441            return true;
442        }
443        false
444    }
445
446    fn push_history(&mut self, kind: TextBoxEditKind, before: CursorPos, after_offset: usize) {
447        let (row, col) = self.cursor;
448        let after = CursorPos::new(row, col, after_offset);
449        let edit = TextBoxEdit::new(kind, before, after);
450        self.history.push(edit);
451    }
452
453    pub fn insert_char(&mut self, c: char) {
454        if c == '\n' || c == '\r' {
455            self.insert_newline();
456            return;
457        }
458
459        self.delete_selection(false);
460        let (row, col) = self.cursor;
461        let line = &mut self.lines[row];
462        let i = line
463            .char_indices()
464            .nth(col)
465            .map(|(i, _)| i)
466            .unwrap_or(line.len());
467        line.insert(i, c);
468        self.cursor.1 += 1;
469        self.push_history(
470            TextBoxEditKind::InsertChar(c),
471            CursorPos::new(row, col, i),
472            i + c.len_utf8(),
473        );
474    }
475
476    pub fn insert_str<S: AsRef<str>>(&mut self, s: S) -> bool {
477        let modified = self.delete_selection(false);
478        let mut lines: Vec<_> = s
479            .as_ref()
480            .split('\n')
481            .map(|s| s.strip_suffix('\r').unwrap_or(s).to_string())
482            .collect();
483        match lines.len() {
484            0 => modified,
485            1 => self.insert_piece(lines.remove(0)),
486            _ => self.insert_chunk(lines),
487        }
488    }
489
490    fn insert_chunk(&mut self, chunk: Vec<String>) -> bool {
491        debug_assert!(chunk.len() > 1, "Chunk size must be > 1: {:?}", chunk);
492
493        let (row, col) = self.cursor;
494        let line = &mut self.lines[row];
495        let i = line
496            .char_indices()
497            .nth(col)
498            .map(|(i, _)| i)
499            .unwrap_or(line.len());
500        let before = CursorPos::new(row, col, i);
501
502        let (row, col) = (
503            row + chunk.len() - 1,
504            chunk[chunk.len() - 1].chars().count(),
505        );
506        self.cursor = (row, col);
507
508        let end_offset = chunk.last().unwrap().len();
509
510        let edit = TextBoxEditKind::InsertChunk(chunk);
511        edit.apply(
512            &mut self.lines,
513            &before,
514            &CursorPos::new(row, col, end_offset),
515        );
516
517        self.push_history(edit, before, end_offset);
518        true
519    }
520
521    fn insert_piece(&mut self, s: String) -> bool {
522        if s.is_empty() {
523            return false;
524        }
525
526        let (row, col) = self.cursor;
527        let line = &mut self.lines[row];
528        debug_assert!(
529            !s.contains('\n'),
530            "string given to TextArea::insert_piece must not contain newline: {:?}",
531            line,
532        );
533
534        let i = line
535            .char_indices()
536            .nth(col)
537            .map(|(i, _)| i)
538            .unwrap_or(line.len());
539        line.insert_str(i, &s);
540        let end_offset = i + s.len();
541
542        self.cursor.1 += s.chars().count();
543        self.push_history(
544            TextBoxEditKind::InsertStr(s),
545            CursorPos::new(row, col, i),
546            end_offset,
547        );
548        true
549    }
550
551    pub fn delete_str(&mut self, chars: usize) -> bool {
552        if self.delete_selection(false) {
553            return true;
554        }
555        if chars == 0 {
556            return false;
557        }
558
559        let (start_row, start_col) = self.cursor;
560
561        let mut remaining = chars;
562        let mut find_end = move |line: &str| {
563            let mut col = 0usize;
564            for (i, _) in line.char_indices() {
565                if remaining == 0 {
566                    return Some((i, col));
567                }
568                col += 1;
569                remaining -= 1;
570            }
571            if remaining == 0 {
572                Some((line.len(), col))
573            } else {
574                remaining -= 1;
575                None
576            }
577        };
578
579        let line = &self.lines[start_row];
580        let start_offset = {
581            line.char_indices()
582                .nth(start_col)
583                .map(|(i, _)| i)
584                .unwrap_or(line.len())
585        };
586
587        // First line
588        if let Some((offset_delta, col_delta)) = find_end(&line[start_offset..]) {
589            let end_offset = start_offset + offset_delta;
590            let end_col = start_col + col_delta;
591            let removed = self.lines[start_row]
592                .drain(start_offset..end_offset)
593                .as_str()
594                .to_string();
595            self.yank = removed.clone().into();
596            self.push_history(
597                TextBoxEditKind::DeleteStr(removed),
598                CursorPos::new(start_row, end_col, end_offset),
599                start_offset,
600            );
601            return true;
602        }
603
604        let mut r = start_row + 1;
605        let mut offset = 0;
606        let mut col = 0;
607
608        while r < self.lines.len() {
609            let line = &self.lines[r];
610            if let Some((o, c)) = find_end(line) {
611                offset = o;
612                col = c;
613                break;
614            }
615            r += 1;
616        }
617
618        let start = CursorPos::new(start_row, start_col, start_offset);
619        let end = CursorPos::new(r, col, offset);
620        self.delete_range(start, end, true);
621        true
622    }
623
624    fn delete_piece(&mut self, col: usize, chars: usize) -> bool {
625        if chars == 0 {
626            return false;
627        }
628
629        #[inline]
630        fn bytes_and_chars(claimed: usize, s: &str) -> (usize, usize) {
631            // Note: `claimed` may be larger than characters in `s` (e.g. usize::MAX)
632            let mut last_col = 0;
633            for (col, (bytes, _)) in s.char_indices().enumerate() {
634                if col == claimed {
635                    return (bytes, claimed);
636                }
637                last_col = col;
638            }
639            (s.len(), last_col + 1)
640        }
641
642        let (row, _) = self.cursor;
643        let line = &mut self.lines[row];
644        if let Some((i, _)) = line.char_indices().nth(col) {
645            let (bytes, chars) = bytes_and_chars(chars, &line[i..]);
646            let removed = line.drain(i..i + bytes).as_str().to_string();
647
648            self.cursor = (row, col);
649            self.push_history(
650                TextBoxEditKind::DeleteStr(removed.clone()),
651                CursorPos::new(row, col + chars, i + bytes),
652                i,
653            );
654            self.yank = removed.into();
655            true
656        } else {
657            false
658        }
659    }
660
661    pub fn insert_tab(&mut self) -> bool {
662        let modified = self.delete_selection(false);
663        if self.tab_len == 0 {
664            return modified;
665        }
666
667        if self.hard_tab_indent {
668            self.insert_char('\t');
669            return true;
670        }
671
672        let (row, col) = self.cursor;
673        let width: usize = self.lines[row]
674            .chars()
675            .take(col)
676            .map(|c| c.width().unwrap_or(0))
677            .sum();
678        let len = self.tab_len - (width % self.tab_len as usize) as u8;
679        self.insert_piece(spaces(len).to_string())
680    }
681
682    pub fn insert_newline(&mut self) {
683        self.delete_selection(false);
684
685        let (row, col) = self.cursor;
686        let line = &mut self.lines[row];
687        let offset = line
688            .char_indices()
689            .nth(col)
690            .map(|(i, _)| i)
691            .unwrap_or(line.len());
692        let next_line = line[offset..].to_string();
693        line.truncate(offset);
694
695        self.lines.insert(row + 1, next_line);
696        self.cursor = (row + 1, 0);
697        self.push_history(
698            TextBoxEditKind::InsertNewline,
699            CursorPos::new(row, col, offset),
700            0,
701        );
702    }
703
704    pub fn delete_newline(&mut self) -> bool {
705        if self.delete_selection(false) {
706            return true;
707        }
708
709        let (row, _) = self.cursor;
710        if row == 0 {
711            return false;
712        }
713
714        let line = self.lines.remove(row);
715        let prev_line = &mut self.lines[row - 1];
716        let prev_line_end = prev_line.len();
717
718        self.cursor = (row - 1, prev_line.chars().count());
719        prev_line.push_str(&line);
720        self.push_history(
721            TextBoxEditKind::DeleteNewline,
722            CursorPos::new(row, 0, 0),
723            prev_line_end,
724        );
725        true
726    }
727
728    pub fn delete_char(&mut self) -> bool {
729        if self.delete_selection(false) {
730            return true;
731        }
732
733        let (row, col) = self.cursor;
734        if col == 0 {
735            return self.delete_newline();
736        }
737
738        let line = &mut self.lines[row];
739        if let Some((offset, c)) = line.char_indices().nth(col - 1) {
740            line.remove(offset);
741            self.cursor.1 -= 1;
742            self.push_history(
743                TextBoxEditKind::DeleteChar(c),
744                CursorPos::new(row, col, offset + c.len_utf8()),
745                offset,
746            );
747            true
748        } else {
749            false
750        }
751    }
752
753    pub fn delete_next_char(&mut self) -> bool {
754        if self.delete_selection(false) {
755            return true;
756        }
757
758        let before = self.cursor;
759        self.move_cursor_with_shift(CursorMove::Forward, false);
760        if before == self.cursor {
761            return false; // Cursor didn't move, meant no character at next of cursor.
762        }
763
764        self.delete_char()
765    }
766
767    pub fn delete_line_by_end(&mut self) -> bool {
768        if self.delete_selection(false) {
769            return true;
770        }
771        if self.delete_piece(self.cursor.1, usize::MAX) {
772            return true;
773        }
774        self.delete_next_char() // At the end of the line. Try to delete next line
775    }
776
777    pub fn delete_line_by_head(&mut self) -> bool {
778        if self.delete_selection(false) {
779            return true;
780        }
781        if self.delete_piece(0, self.cursor.1) {
782            return true;
783        }
784        self.delete_newline()
785    }
786
787    pub fn delete_word(&mut self) -> bool {
788        if self.delete_selection(false) {
789            return true;
790        }
791        let (r, c) = self.cursor;
792        if let Some(col) = find_word_start_backward(&self.lines[r], c) {
793            self.delete_piece(col, c - col)
794        } else if c > 0 {
795            self.delete_piece(0, c)
796        } else {
797            self.delete_newline()
798        }
799    }
800
801    pub fn delete_next_word(&mut self) -> bool {
802        if self.delete_selection(false) {
803            return true;
804        }
805        let (r, c) = self.cursor;
806        let line = &self.lines[r];
807        if let Some(col) = find_word_end_forward(line, c) {
808            self.delete_piece(c, col - c)
809        } else {
810            let end_col = line.chars().count();
811            if c < end_col {
812                self.delete_piece(c, end_col - c)
813            } else if r + 1 < self.lines.len() {
814                self.cursor = (r + 1, 0);
815                self.delete_newline()
816            } else {
817                false
818            }
819        }
820    }
821
822    pub fn select_all(&mut self) {
823        self.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
824        self.selection_start = Some((0, 0));
825    }
826
827    pub fn move_cursor(&mut self, m: CursorMove) {
828        self.move_cursor_with_shift(m, self.selection_start.is_some());
829    }
830
831    pub fn undo(&mut self) -> bool {
832        if let Some(cursor) = self.history.undo(&mut self.lines) {
833            self.cancel_selection();
834            self.cursor = cursor;
835            true
836        } else {
837            false
838        }
839    }
840
841    pub fn redo(&mut self) -> bool {
842        if let Some(cursor) = self.history.redo(&mut self.lines) {
843            self.cancel_selection();
844            self.cursor = cursor;
845            true
846        } else {
847            false
848        }
849    }
850
851    pub(crate) fn get_formatted_line<'b>(
852        &'b self,
853        line: &'b str,
854        row: usize,
855        line_num_len: u8,
856    ) -> Line<'b> {
857        let mut hl = TextLineFormatter::new(
858            line,
859            self.cursor_style,
860            self.tab_len,
861            self.mask,
862            self.select_style,
863        );
864
865        if let Some(style) = self.line_number_style {
866            hl.line_number(row, line_num_len, style);
867        }
868
869        if row == self.cursor.0 {
870            hl.cursor_line(self.cursor.1, self.cursor_line_style);
871        }
872
873        if let Some((start, end)) = self.selection_range() {
874            hl.selection(row, start.row, start.offset, end.row, end.offset);
875        }
876
877        hl.into_line()
878    }
879
880    pub fn widget(&'a self) -> impl Widget + 'a {
881        TextBoxRenderer::new(self)
882    }
883
884    pub fn style(&self) -> Style {
885        self.style
886    }
887
888    pub fn set_block(&mut self, block: Block<'a>) {
889        self.block = Some(block);
890    }
891
892    pub fn block<'s>(&'s self) -> Option<&'s Block<'a>> {
893        self.block.as_ref()
894    }
895
896    pub fn set_line_number_style(&mut self, style: Style) {
897        self.line_number_style = Some(style);
898    }
899
900    pub fn remove_line_number(&mut self) {
901        self.line_number_style = None;
902    }
903
904    pub fn lines(&'a self) -> &'a [String] {
905        &self.lines
906    }
907
908    pub fn cursor(&self) -> (usize, usize) {
909        self.cursor
910    }
911
912    pub fn set_alignment(&mut self, alignment: Alignment) {
913        if let Alignment::Center | Alignment::Right = alignment {
914            self.line_number_style = None;
915        }
916        self.alignment = alignment;
917    }
918
919    pub fn alignment(&self) -> Alignment {
920        self.alignment
921    }
922
923    pub fn is_empty(&self) -> bool {
924        self.lines == [""]
925    }
926
927    pub fn scroll(&mut self, scrolling: impl Into<TextBoxScroll>) {
928        self.scroll_with_shift(scrolling.into(), self.selection_start.is_some());
929    }
930
931    fn scroll_with_shift(&mut self, scrolling: TextBoxScroll, shift: bool) {
932        if shift && self.selection_start.is_none() {
933            self.selection_start = Some(self.cursor);
934        }
935        scrolling.scroll(&mut self.viewport);
936        self.move_cursor_with_shift(CursorMove::InViewport, shift);
937    }
938
939    pub fn start_selection(&mut self) {
940        self.selection_start = Some(self.cursor);
941    }
942
943    pub fn cancel_selection(&mut self) {
944        self.selection_start = None;
945    }
946
947    fn move_cursor_with_shift(&mut self, m: CursorMove, shift: bool) {
948        if let Some(cursor) = m.next_cursor(self.cursor, &self.lines, &self.viewport) {
949            if shift {
950                if self.selection_start.is_none() {
951                    self.start_selection();
952                }
953            } else {
954                self.cancel_selection();
955            }
956            self.cursor = cursor;
957        } else {
958            log::debug!("Cursor move failed: {:?}", m);
959        }
960    }
961
962    pub fn get_non_ascii_aware_cursor_x_pos(&self) -> usize {
963        let (row, col) = self.cursor;
964        let line = &self.lines[row];
965        let mut raw_length = 0;
966        for c in line.chars().take(col) {
967            raw_length += c.width().unwrap_or_default();
968        }
969        raw_length
970    }
971}