rat_text/
text_area.rs

1//!
2//! A text-area widget with text-styling abilities.
3//! And undo + clipboard support.
4//!
5
6use crate::_private::NonExhaustive;
7use crate::clipboard::{Clipboard, global_clipboard};
8use crate::event::{ReadOnly, TextOutcome};
9#[allow(deprecated)]
10use crate::glyph::Glyph;
11use crate::glyph2::{GlyphIter2, TextWrap2};
12use crate::grapheme::Grapheme;
13use crate::text_core::TextCore;
14use crate::text_store::TextStore;
15use crate::text_store::text_rope::TextRope;
16use crate::undo_buffer::{UndoBuffer, UndoEntry, UndoVec};
17use crate::{
18    Cursor, HasScreenCursor, TextError, TextPosition, TextRange, TextStyle, ipos_type, upos_type,
19};
20use crossterm::event::KeyModifiers;
21use rat_event::util::MouseFlags;
22use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
23use rat_focus::event::FocusTraversal;
24use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
25use rat_reloc::{RelocatableState, relocate_area, relocate_dark_offset, relocate_pos_tuple};
26use rat_scrolled::event::ScrollOutcome;
27use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState};
28use ratatui::buffer::Buffer;
29use ratatui::layout::{Rect, Size};
30use ratatui::style::{Style, Stylize};
31use ratatui::widgets::{Block, StatefulWidget};
32use ropey::Rope;
33use std::borrow::Cow;
34use std::cmp::{max, min};
35use std::collections::HashMap;
36use std::ops::Range;
37
38/// Text area widget.
39///
40/// [Example](https://github.com/thscharler/rat-salsa/blob/master/rat-text/examples/textarea2.rs)
41///
42/// Backend used is [ropey](https://docs.rs/ropey/latest/ropey/), so large
43/// texts are no problem. Editing time increases with the number of
44/// styles applied. Everything below a million styles should be fine.
45///
46/// For emoji support this uses
47/// [unicode_display_width](https://docs.rs/unicode-display-width/latest/unicode_display_width/index.html)
48/// which helps with those double-width emojis. Input of emojis
49/// strongly depends on the terminal. It may or may not work.
50/// And even with display there are sometimes strange glitches
51/// that I haven't found yet.
52///
53/// Keyboard and mouse are implemented for crossterm, but it should be
54/// easy to extend to other event-types. Every interaction is available
55/// as function on the state.
56///
57/// Scrolling doesn't depend on the cursor, but the editing and move
58/// functions take care that the cursor stays visible.
59///
60/// Text wrapping is available, hard line-breaks at the right margin,
61/// or decent word-wrapping.
62///
63/// You can directly access the underlying Rope for readonly purposes, and
64/// conversion from/to byte/char positions are available. That should probably be
65/// enough to write a parser that generates some styling.
66///
67/// The cursor must set externally on the ratatui Frame as usual.
68/// [screen_cursor](TextAreaState::screen_cursor) gives you the correct value.
69/// There is the inverse too [set_screen_cursor](TextAreaState::set_screen_cursor)
70/// For more interactions you can use [screen_to_pos](TextAreaState::screen_to_pos),
71/// and [pos_to_screen](TextAreaState::pos_to_screen). They calculate everything,
72/// even in the presence of more complex graphemes and those double-width emojis.
73///
74/// # Stateful
75/// This widget implements [`StatefulWidget`], you can use it with
76/// [`TextAreaState`] to handle common actions.
77#[derive(Debug, Default, Clone)]
78pub struct TextArea<'a> {
79    block: Option<Block<'a>>,
80    hscroll: Option<Scroll<'a>>,
81    h_max_offset: Option<usize>,
82    h_overscroll: Option<usize>,
83    vscroll: Option<Scroll<'a>>,
84
85    style: Style,
86    focus_style: Option<Style>,
87    select_style: Option<Style>,
88    text_style: HashMap<usize, Style>,
89}
90
91/// State & event handling.
92#[derive(Debug)]
93pub struct TextAreaState {
94    /// The whole area with block.
95    /// __read only__ renewed with each render.
96    pub area: Rect,
97    /// Area inside a possible block.
98    /// __read only__ renewed with each render.
99    pub inner: Rect,
100    /// Rendered dimension. This may differ from (inner.width, inner.height)
101    /// if the text area has been relocated/clipped. This holds the
102    /// original rendered dimension before any relocation/clipping.
103    pub rendered: Size,
104    /// Cursor position on the screen.
105    pub screen_cursor: Option<(u16, u16)>,
106
107    /// Horizontal scroll.
108    /// When text-break is active this value is ignored.
109    /// __read+write__
110    pub hscroll: ScrollState,
111    /// Vertical offset.
112    /// __read+write__
113    pub vscroll: ScrollState,
114    /// When text-break is active, this is the grapheme-offset
115    /// into the first visible text-row where the display
116    /// actually starts.
117    /// __read+write__ but it's not advised.
118    pub sub_row_offset: upos_type,
119    /// Dark offset due to clipping.
120    /// __read only__ secondary offset due to clipping.
121    pub dark_offset: (u16, u16),
122    /// The scroll offset will be adjusted to display
123    /// the cursor. This will be the minimal adjustment,
124    /// the cursor will stay at the same screen position if
125    /// it's already visible or appear at the start/end if it's not.
126    /// __read+write__ use scroll_cursor_to_visible().
127    pub scroll_to_cursor: bool,
128
129    /// Text edit core
130    pub value: TextCore<TextRope>,
131
132    /// Memory-column for up/down movement.
133    ///
134    /// Up/down movement tries to place the cursor at this column,
135    /// but might have to clip it, because the current line is too short.
136    ///
137    /// This is kept as a relative screen-position. It may be less
138    /// than 0, if the widget has been relocated.
139    pub move_col: Option<i16>,
140    /// auto indent active
141    pub auto_indent: bool,
142    /// quote selection active
143    pub auto_quote: bool,
144    /// text breaking
145    pub text_wrap: TextWrap,
146
147    /// Current focus state.
148    pub focus: FocusFlag,
149
150    /// Mouse selection in progress.
151    /// __read+write__
152    pub mouse: MouseFlags,
153
154    pub non_exhaustive: NonExhaustive,
155}
156
157impl Clone for TextAreaState {
158    fn clone(&self) -> Self {
159        Self {
160            area: self.area,
161            inner: self.inner,
162            rendered: self.rendered,
163            screen_cursor: self.screen_cursor,
164            hscroll: self.hscroll.clone(),
165            vscroll: self.vscroll.clone(),
166            sub_row_offset: self.sub_row_offset,
167            dark_offset: self.dark_offset,
168            scroll_to_cursor: self.scroll_to_cursor,
169            value: self.value.clone(),
170            move_col: None,
171            auto_indent: self.auto_indent,
172            auto_quote: self.auto_quote,
173            text_wrap: self.text_wrap,
174            focus: FocusFlag::named(self.focus.name()),
175            mouse: Default::default(),
176            non_exhaustive: NonExhaustive,
177        }
178    }
179}
180
181/// Text breaking.
182#[derive(Debug, Clone, Copy)]
183#[non_exhaustive]
184pub enum TextWrap {
185    /// Don't break, shift text to the left.
186    Shift,
187    /// Hard break at the right border.
188    Hard,
189    /// Wraps the text at word boundaries.
190    ///
191    /// The parameter gives an area before the right border where
192    /// breaks are preferred. The first space that falls in this
193    /// region will break. Otherwise, the last space before will be
194    /// used, or the word will be hard-wrapped.
195    ///
196    /// Space is the word-separator. Words will be broken if they
197    /// contain a hyphen, a soft-hyphen or a zero-width-space.
198    Word(u16),
199}
200
201impl<'a> TextArea<'a> {
202    /// New widget.
203    pub fn new() -> Self {
204        Self::default()
205    }
206
207    /// Set the combined style.
208    #[inline]
209    pub fn styles_opt(self, styles: Option<TextStyle>) -> Self {
210        if let Some(styles) = styles {
211            self.styles(styles)
212        } else {
213            self
214        }
215    }
216
217    /// Set the combined style.
218    #[inline]
219    pub fn styles(mut self, styles: TextStyle) -> Self {
220        self.style = styles.style;
221        if styles.focus.is_some() {
222            self.focus_style = styles.focus;
223        }
224        if styles.select.is_some() {
225            self.select_style = styles.select;
226        }
227        if let Some(border_style) = styles.border_style {
228            self.block = self.block.map(|v| v.border_style(border_style));
229        }
230        self.block = self.block.map(|v| v.style(self.style));
231        if styles.block.is_some() {
232            self.block = styles.block;
233        }
234        if let Some(styles) = styles.scroll {
235            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
236            self.vscroll = self.vscroll.map(|v| v.styles(styles));
237        }
238        self
239    }
240
241    /// Base style.
242    pub fn style(mut self, style: Style) -> Self {
243        self.style = style;
244        self
245    }
246
247    /// Style when focused.
248    pub fn focus_style(mut self, style: Style) -> Self {
249        self.focus_style = Some(style);
250        self.block = self.block.map(|v| v.style(self.style));
251        self
252    }
253
254    /// Selection style.
255    pub fn select_style(mut self, style: Style) -> Self {
256        self.select_style = Some(style);
257        self
258    }
259
260    /// Indexed text-style.
261    ///
262    /// Use [TextAreaState::add_style()] to refer a text range to
263    /// one of these styles.
264    pub fn text_style_idx(mut self, idx: usize, style: Style) -> Self {
265        self.text_style.insert(idx, style);
266        self
267    }
268
269    /// List of text-styles.
270    ///
271    /// Use [TextAreaState::add_style()] to refer a text range to
272    /// one of these styles.
273    pub fn text_style<T: IntoIterator<Item = Style>>(mut self, styles: T) -> Self {
274        for (i, s) in styles.into_iter().enumerate() {
275            self.text_style.insert(i, s);
276        }
277        self
278    }
279
280    /// Map of style_id -> text_style.
281    ///
282    /// Use [TextAreaState::add_style()] to refer a text range to
283    /// one of these styles.
284    pub fn text_style_map<T: Into<Style>>(mut self, styles: HashMap<usize, T>) -> Self {
285        for (i, s) in styles.into_iter() {
286            self.text_style.insert(i, s.into());
287        }
288        self
289    }
290
291    /// Block.
292    #[inline]
293    pub fn block(mut self, block: Block<'a>) -> Self {
294        self.block = Some(block);
295        self
296    }
297
298    /// Set both scrollbars.
299    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
300        self.hscroll = Some(scroll.clone().override_horizontal());
301        self.vscroll = Some(scroll.override_vertical());
302        self
303    }
304
305    /// Set the horizontal scrollbar.
306    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
307        self.hscroll = Some(scroll.override_horizontal());
308        self
309    }
310
311    /// Maximum offset the horizontal scrollbar.
312    ///
313    /// This widget doesn't try to find a correct maximum value
314    /// to show with the horizontal scroll bar, but uses this
315    /// fixed value instead. This is the maximum offset that can
316    /// be reached by using the scrollbar.
317    ///
318    /// Finding the maximum line length for a text is rather
319    /// expensive, so this widget doesn't even try.
320    ///
321    /// This doesn't limit the column that can be reached with
322    /// cursor positioning, just what can be done via the scrollbar.
323    ///
324    /// See [self.set_horizontal_overscroll]
325    ///
326    /// Default is 255.
327    pub fn set_horizontal_max_offset(mut self, offset: usize) -> Self {
328        self.h_max_offset = Some(offset);
329        self
330    }
331
332    /// Maximum overscroll that can be reached by using the horizontal
333    /// scrollbar and dragging beyond the area of the widget.
334    ///
335    /// See [self.set_horizontal_max_offset]
336    ///
337    /// Default is 16384.
338    pub fn set_horizontal_overscroll(mut self, overscroll: usize) -> Self {
339        self.h_overscroll = Some(overscroll);
340        self
341    }
342
343    /// Set the vertical scrollbar.
344    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
345        self.vscroll = Some(scroll.override_vertical());
346        self
347    }
348}
349
350impl<'a> StatefulWidget for &TextArea<'a> {
351    type State = TextAreaState;
352
353    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
354        render_text_area(self, area, buf, state);
355    }
356}
357
358impl StatefulWidget for TextArea<'_> {
359    type State = TextAreaState;
360
361    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
362        render_text_area(&self, area, buf, state);
363    }
364}
365
366fn render_text_area(
367    widget: &TextArea<'_>,
368    area: Rect,
369    buf: &mut Buffer,
370    state: &mut TextAreaState,
371) {
372    state.area = area;
373    state.screen_cursor = None;
374
375    let style = widget.style;
376    let select_style = if let Some(select_style) = widget.select_style {
377        // if the select_style has no fg/bg don't patch
378        if select_style.fg.is_none() && select_style.bg.is_none() {
379            select_style
380        } else {
381            style.patch(select_style)
382        }
383    } else {
384        Style::default().black().on_yellow()
385    };
386
387    // sync scroll and cursor
388    state.area = area;
389    state.screen_cursor = None;
390    state.inner = ScrollArea::new()
391        .block(widget.block.as_ref())
392        .h_scroll(widget.hscroll.as_ref())
393        .v_scroll(widget.vscroll.as_ref())
394        .inner(area, Some(&state.hscroll), Some(&state.vscroll));
395    state.rendered = state.inner.as_size();
396
397    if let Some(h_max_offset) = widget.h_max_offset {
398        state.hscroll.set_max_offset(h_max_offset);
399    }
400    if let Some(h_overscroll) = widget.h_overscroll {
401        state.hscroll.set_overscroll_by(Some(h_overscroll));
402    }
403    state.hscroll.set_page_len(state.inner.width as usize);
404    if let TextWrap::Hard | TextWrap::Word(_) = state.text_wrap {
405        state
406            .vscroll
407            .set_max_offset(state.len_lines().saturating_sub(1) as usize);
408    } else {
409        state.vscroll.set_max_offset(
410            state
411                .len_lines()
412                .saturating_sub(state.inner.height as upos_type) as usize,
413        );
414    }
415    state.vscroll.set_page_len(state.inner.height as usize);
416
417    if state.scroll_to_cursor {
418        state.scroll_to_cursor();
419    }
420
421    // scroll + background
422    ScrollArea::new()
423        .block(widget.block.as_ref())
424        .h_scroll(widget.hscroll.as_ref())
425        .v_scroll(widget.vscroll.as_ref())
426        .style(style)
427        .render(
428            area,
429            buf,
430            &mut ScrollAreaState::new()
431                .h_scroll(&mut state.hscroll)
432                .v_scroll(&mut state.vscroll),
433        );
434
435    if state.inner.width == 0 || state.inner.height == 0 {
436        // noop
437        return;
438    }
439    if state.vscroll.offset() > state.value.len_lines() as usize {
440        // noop
441        return;
442    }
443
444    let (shift_left, sub_row_offset, start_row) = state.clean_offset();
445    let page_rows = start_row
446        ..min(
447            start_row + state.inner.height as upos_type,
448            state.value.len_lines(),
449        );
450    let page_bytes = state
451        .try_bytes_at_range(TextRange::new(
452            (sub_row_offset, page_rows.start),
453            (0, page_rows.end),
454        ))
455        .expect("valid_rows");
456    let mut screen_cursor = None;
457    let selection = state.selection();
458    let mut styles = Vec::new();
459
460    for g in state
461        .glyphs2(shift_left, sub_row_offset, page_rows)
462        .expect("valid_offset")
463    {
464        // relative screen-pos of the glyph
465        let screen_pos = g.screen_pos();
466
467        if screen_pos.1 >= state.inner.height {
468            break;
469        }
470
471        // ignore synthetic glyphs used for wrapping.
472        if g.pos() == state.cursor() && !g.soft_break() {
473            screen_cursor = Some(g.screen_pos());
474        }
475
476        if g.screen_width() > 0 {
477            let mut style = style;
478            // text-styles
479            state
480                .value
481                .styles_at_page(g.text_bytes().start, page_bytes.clone(), &mut styles);
482            for style_nr in &styles {
483                if let Some(s) = widget.text_style.get(style_nr) {
484                    style = style.patch(*s);
485                }
486            }
487            // selection
488            if selection.contains_pos(g.pos()) {
489                style = style.patch(select_style);
490            };
491
492            // render glyph
493            if let Some(cell) =
494                buf.cell_mut((state.inner.x + screen_pos.0, state.inner.y + screen_pos.1))
495            {
496                cell.set_symbol(g.glyph());
497                cell.set_style(style);
498            }
499            // clear the reset of the cells to avoid interferences.
500            for d in 1..g.screen_width() {
501                if let Some(cell) = buf.cell_mut((
502                    state.inner.x + screen_pos.0 + d,
503                    state.inner.y + screen_pos.1,
504                )) {
505                    cell.reset();
506                    cell.set_style(style);
507                }
508            }
509        }
510    }
511
512    state.screen_cursor = screen_cursor.map(|v| (state.inner.x + v.0, state.inner.y + v.1));
513}
514
515impl Default for TextAreaState {
516    fn default() -> Self {
517        let mut s = Self {
518            area: Default::default(),
519            inner: Default::default(),
520            rendered: Default::default(),
521            screen_cursor: Default::default(),
522            hscroll: Default::default(),
523            vscroll: Default::default(),
524            sub_row_offset: 0,
525            dark_offset: Default::default(),
526            scroll_to_cursor: Default::default(),
527            value: TextCore::new(Some(Box::new(UndoVec::new(99))), Some(global_clipboard())),
528            move_col: Default::default(),
529            auto_indent: true,
530            auto_quote: true,
531            text_wrap: TextWrap::Shift,
532            focus: Default::default(),
533            mouse: Default::default(),
534            non_exhaustive: NonExhaustive,
535        };
536        s.hscroll.set_max_offset(255);
537        s.hscroll.set_overscroll_by(Some(16384));
538        s
539    }
540}
541
542impl HasFocus for TextAreaState {
543    fn build(&self, builder: &mut FocusBuilder) {
544        builder.leaf_widget(self);
545    }
546
547    fn focus(&self) -> FocusFlag {
548        self.focus.clone()
549    }
550
551    fn area(&self) -> Rect {
552        self.area
553    }
554
555    fn navigable(&self) -> Navigation {
556        Navigation::Reach
557    }
558}
559
560impl TextAreaState {
561    /// New State.
562    #[inline]
563    pub fn new() -> Self {
564        Self::default()
565    }
566
567    /// New state with a focus name.
568    #[inline]
569    pub fn named(name: &str) -> Self {
570        Self {
571            focus: FocusFlag::named(name),
572            ..Default::default()
573        }
574    }
575
576    /// Sets the line ending used for insert.
577    /// There is no auto-detection or conversion done for set_value().
578    ///
579    /// Caution: If this doesn't match the line ending used in the value, you
580    /// will get a value with mixed line endings.
581    #[inline]
582    pub fn set_newline(&mut self, br: impl Into<String>) {
583        self.value.set_newline(br.into());
584    }
585
586    /// Line ending used for insert.
587    #[inline]
588    pub fn newline(&self) -> &str {
589        self.value.newline()
590    }
591
592    /// Sets auto-indent on new-line.
593    #[inline]
594    pub fn set_auto_indent(&mut self, indent: bool) {
595        self.auto_indent = indent;
596    }
597
598    /// Activates 'add quotes to selection'.
599    #[inline]
600    pub fn set_auto_quote(&mut self, quote: bool) {
601        self.auto_quote = quote;
602    }
603
604    /// Set tab-width.
605    #[inline]
606    pub fn set_tab_width(&mut self, tabs: u16) {
607        self.value.set_tab_width(tabs);
608    }
609
610    /// Tab-width
611    #[inline]
612    pub fn tab_width(&self) -> u16 {
613        self.value.tab_width()
614    }
615
616    /// Expand tabs to spaces. Only for new inputs.
617    #[inline]
618    pub fn set_expand_tabs(&mut self, expand: bool) {
619        self.value.set_expand_tabs(expand);
620    }
621
622    /// Expand tabs to spaces. Only for new inputs.
623    #[inline]
624    pub fn expand_tabs(&self) -> bool {
625        self.value.expand_tabs()
626    }
627
628    /// Show glyphs for control characters.
629    #[inline]
630    pub fn set_show_ctrl(&mut self, show_ctrl: bool) {
631        self.value.set_glyph_ctrl(show_ctrl);
632    }
633
634    /// Show glyphs for control characters.
635    pub fn show_ctrl(&self) -> bool {
636        self.value.glyph_ctrl()
637    }
638
639    /// Show glyphs for text-wrapping.
640    /// Shows inserted line-breaks, zero-width space (U+200B) or the soft hyphen (U+00AD).
641    #[inline]
642    pub fn set_wrap_ctrl(&mut self, wrap_ctrl: bool) {
643        self.value.set_wrap_ctrl(wrap_ctrl);
644    }
645
646    /// Show glyphs for text-wrapping.
647    /// Shows inserted line-breaks, zero-width space (U+200B) or the soft hyphen (U+00AD).
648    pub fn wrap_ctrl(&self) -> bool {
649        self.value.wrap_ctrl()
650    }
651
652    /// Text wrapping mode.
653    ///
654    /// * TextWrap::Shift means no wrapping.
655    /// * TextWrap::Hard hard-wraps at the right border.
656    /// * TextWrap::Word(n) does word wrapping.
657    ///   n gives the size of the break-region close to the right border.
658    ///   The first space that falls in this region is taken as a break.
659    ///   If that is not possible this will break at the last space before.
660    ///   If there is no space it will hard-wrap the word.
661    ///
662    ///   Space is used as word separator. Hyphen will be used to break
663    ///   a word, and soft-hyphen and zero-width-space will be recognized too.
664    pub fn set_text_wrap(&mut self, text_wrap: TextWrap) {
665        self.text_wrap = text_wrap;
666    }
667
668    /// Text wrapping.
669    pub fn text_wrap(&self) -> TextWrap {
670        self.text_wrap
671    }
672
673    /// Extra column information for cursor movement.
674    ///
675    /// The cursor position is capped to the current line length, so if you
676    /// move up one row, you might end at a position left of the current column.
677    /// If you move up once more you want to return to the original position.
678    /// That's what is stored here.
679    ///
680    /// This stores the relative screen-column, it may be less than 0
681    /// if the widget has been relocated.
682    #[inline]
683    pub fn set_move_col(&mut self, col: Option<i16>) {
684        self.move_col = col;
685    }
686
687    /// Extra column information for cursor movement.
688    #[inline]
689    pub fn move_col(&mut self) -> Option<i16> {
690        self.move_col
691    }
692}
693
694impl TextAreaState {
695    /// Clipboard used.
696    /// Default is to use the global_clipboard().
697    #[inline]
698    pub fn set_clipboard(&mut self, clip: Option<impl Clipboard + 'static>) {
699        match clip {
700            None => self.value.set_clipboard(None),
701            Some(v) => self.value.set_clipboard(Some(Box::new(v))),
702        }
703    }
704
705    /// Clipboard used.
706    /// Default is to use the global_clipboard().
707    #[inline]
708    pub fn clipboard(&self) -> Option<&dyn Clipboard> {
709        self.value.clipboard()
710    }
711
712    /// Copy selection to clipboard.
713    #[inline]
714    pub fn copy_to_clip(&mut self) -> bool {
715        let Some(clip) = self.value.clipboard() else {
716            return false;
717        };
718
719        _ = clip.set_string(self.selected_text().as_ref());
720        false
721    }
722
723    /// Cut selection to clipboard.
724    #[inline]
725    pub fn cut_to_clip(&mut self) -> bool {
726        let Some(clip) = self.value.clipboard() else {
727            return false;
728        };
729
730        match clip.set_string(self.selected_text().as_ref()) {
731            Ok(_) => self.delete_range(self.selection()),
732            Err(_) => false,
733        }
734    }
735
736    /// Paste from clipboard.
737    #[inline]
738    pub fn paste_from_clip(&mut self) -> bool {
739        let Some(clip) = self.value.clipboard() else {
740            return false;
741        };
742
743        if let Ok(text) = clip.get_string() {
744            self.insert_str(text)
745        } else {
746            false
747        }
748    }
749}
750
751impl TextAreaState {
752    /// Set the undo buffer.
753    ///
754    /// Default is an undo-buffer with 99 undoes.
755    ///
756    /// Adjacent edits will be merged automatically into a single undo.
757    /// (Type multiple characters, they will be undone in one go.)
758    #[inline]
759    pub fn set_undo_buffer(&mut self, undo: Option<impl UndoBuffer + 'static>) {
760        match undo {
761            None => self.value.set_undo_buffer(None),
762            Some(v) => self.value.set_undo_buffer(Some(Box::new(v))),
763        }
764    }
765
766    /// Access the undo buffer.
767    #[inline]
768    pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
769        self.value.undo_buffer()
770    }
771
772    /// Access the undo buffer.
773    #[inline]
774    pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
775        self.value.undo_buffer_mut()
776    }
777
778    /// Begin a sequence of changes that should be undone in one go.
779    ///
780    /// Call begin_undo_seq(), then call any edit-functions. When
781    /// you are done call end_undo_seq(). Any changes will be undone
782    /// in a single step.
783    #[inline]
784    pub fn begin_undo_seq(&mut self) {
785        self.value.begin_undo_seq()
786    }
787
788    /// End a sequence of changes that should be undone in one go.
789    #[inline]
790    pub fn end_undo_seq(&mut self) {
791        self.value.end_undo_seq()
792    }
793
794    /// Get all recent replay recordings. This log can be sent
795    /// to a second TextAreaState and can be applied with replay_log().
796    ///
797    /// There are some [caveats](UndoBuffer::enable_replay_log).
798    #[inline]
799    pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
800        self.value.recent_replay_log()
801    }
802
803    /// Apply the replay recording.
804    ///
805    /// There are some [caveats](UndoBuffer::enable_replay_log).
806    #[inline]
807    pub fn replay_log(&mut self, replay: &[UndoEntry]) {
808        self.value.replay_log(replay)
809    }
810
811    /// Do one undo.
812    #[inline]
813    pub fn undo(&mut self) -> bool {
814        self.value.undo()
815    }
816
817    /// Do one redo.
818    #[inline]
819    pub fn redo(&mut self) -> bool {
820        self.value.redo()
821    }
822}
823
824impl TextAreaState {
825    /// Set and replace all styles.
826    ///
827    /// The ranges are byte-ranges into the text. There is no
828    /// verification that the ranges fit the text.
829    ///
830    /// Each byte-range maps to an index into the styles set
831    /// with the widget.
832    ///
833    /// Any style-idx that don't have a match there are just
834    /// ignored. You can use this to store other range based information.
835    /// The ranges are corrected during edits, no need to recalculate
836    /// everything after each keystroke.
837    ///
838    /// But this is only a very basic correction based on
839    /// insertions and deletes. If you use this for syntax-highlighting
840    /// you probably need to rebuild the styles.
841    #[inline]
842    pub fn set_styles(&mut self, styles: Vec<(Range<usize>, usize)>) {
843        self.value.set_styles(styles);
844    }
845
846    /// Set and replace all styles.
847    ///
848    /// The ranges are TextRanges into the text.
849    /// Each byte-range maps to an index into the styles set
850    /// with the widget.
851    ///
852    /// Any style-idx that don't have a match there are just
853    /// ignored. You can use this to store other range based information.
854    /// The ranges are corrected during edits, no need to recalculate
855    /// everything after each keystroke.
856    #[inline]
857    pub fn set_range_styles(&mut self, styles: Vec<(TextRange, usize)>) -> Result<(), TextError> {
858        self.value.set_range_styles(styles)
859    }
860
861    /// Add a style for a byte-range.
862    ///
863    /// The style-idx refers to one of the styles set with the widget.
864    /// Missing styles are just ignored.
865    #[inline]
866    pub fn add_style(&mut self, range: Range<usize>, style: usize) {
867        self.value.add_style(range, style);
868    }
869
870    /// Add a style for a [TextRange]. The style-nr refers to one
871    /// of the styles set with the widget.
872    /// Missing styles are just ignored.
873    #[inline]
874    pub fn add_range_style(&mut self, range: TextRange, style: usize) -> Result<(), TextError> {
875        let r = self.value.bytes_at_range(range)?;
876        self.value.add_style(r, style);
877        Ok(())
878    }
879
880    /// Remove the exact byte-range and style.
881    #[inline]
882    pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
883        self.value.remove_style(range, style);
884    }
885
886    /// Remove the exact TextRange and style.
887    #[inline]
888    pub fn remove_range_style(&mut self, range: TextRange, style: usize) -> Result<(), TextError> {
889        let r = self.value.bytes_at_range(range)?;
890        self.value.remove_style(r, style);
891        Ok(())
892    }
893
894    /// Find all styles that touch the given range.
895    pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
896        self.value.styles_in(range, buf)
897    }
898
899    /// All styles active at the given position.
900    #[inline]
901    pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
902        self.value.styles_at(byte_pos, buf)
903    }
904
905    /// Check if the given style applies at the position and
906    /// return the complete range for the style.
907    #[inline]
908    pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
909        self.value.style_match(byte_pos, style)
910    }
911
912    /// List of all styles.
913    #[inline]
914    pub fn styles(&self) -> impl Iterator<Item = (Range<usize>, usize)> + '_ {
915        self.value.styles().expect("styles")
916    }
917}
918
919impl TextAreaState {
920    /// Current offset for scrolling.
921    #[inline]
922    pub fn offset(&self) -> (usize, usize) {
923        (self.hscroll.offset(), self.vscroll.offset())
924    }
925
926    /// Set the offset for scrolling.
927    ///
928    /// The offset uses usize, but it shouldn't exceed (u32::MAX, u32::MAX).
929    /// This is due to the internal ScrollState that only knows usize.
930    #[inline]
931    pub fn set_offset(&mut self, offset: (usize, usize)) -> bool {
932        self.scroll_to_cursor = false;
933        let c = self.hscroll.set_offset(offset.0);
934        let r = self.vscroll.set_offset(offset.1);
935        r || c
936    }
937
938    /// Scrolling uses the offset() for the start of the displayed text.
939    /// When text-wrapping is active, this is not enough. This gives
940    /// an extra offset into the first row where rendering should start.
941    ///
942    /// You probably don't want to set this value. Use set_cursor()
943    /// instead, it will automatically scroll to make the cursor visible.
944    ///
945    /// If you really want, pos_to_line_start() can help to find the
946    /// start-position of a visual row. The x-value of the returned
947    /// TextPosition is a valid value for this function.
948    ///
949    pub fn set_sub_row_offset(&mut self, sub_row_offset: upos_type) -> bool {
950        self.scroll_to_cursor = false;
951        let old = self.sub_row_offset;
952        self.sub_row_offset = sub_row_offset;
953        sub_row_offset != old
954    }
955
956    /// Returns the extra offset into the first row where rendering
957    /// starts. This is only valid if text-wrapping is active.
958    ///
959    /// Returns the index of the column.
960    pub fn sub_row_offset(&self) -> upos_type {
961        self.sub_row_offset
962    }
963
964    /// This returns the triple (hscroll.offset, sub_row_offset, vscroll.offset )
965    /// all trimmed to upos_type. sub_row_offset will only have a value if
966    /// there is some text-wrapping active. hscroll.offset will only have
967    /// an offset if there is *no* text-wrapping active.
968    fn clean_offset(&self) -> (upos_type, upos_type, upos_type) {
969        let ox = self.hscroll.offset as upos_type;
970        let mut oy = self.vscroll.offset as upos_type;
971
972        // reset invalid offset
973        if oy >= self.len_lines() {
974            oy = 0;
975        }
976
977        match self.text_wrap {
978            TextWrap::Shift => (ox, 0, oy),
979            TextWrap::Hard | TextWrap::Word(_) => {
980                // sub_row_offset can be any value. limit somewhat.
981                if let Ok(max_col) = self.try_line_width(oy) {
982                    (0, min(self.sub_row_offset, max_col), oy)
983                } else {
984                    (0, 0, oy)
985                }
986            }
987        }
988    }
989
990    /// Cursor position.
991    #[inline]
992    pub fn cursor(&self) -> TextPosition {
993        self.value.cursor()
994    }
995
996    /// Set the cursor position and scroll the cursor to a visible offset.
997    #[inline]
998    pub fn set_cursor(&mut self, cursor: impl Into<TextPosition>, extend_selection: bool) -> bool {
999        self.scroll_cursor_to_visible();
1000        self.value.set_cursor(cursor.into(), extend_selection)
1001    }
1002
1003    /// Selection anchor.
1004    #[inline]
1005    pub fn anchor(&self) -> TextPosition {
1006        self.value.anchor()
1007    }
1008
1009    /// Has a selection?
1010    #[inline]
1011    pub fn has_selection(&self) -> bool {
1012        self.value.has_selection()
1013    }
1014
1015    /// Current selection.
1016    #[inline]
1017    pub fn selection(&self) -> TextRange {
1018        self.value.selection()
1019    }
1020
1021    /// Set the selection, anchor and cursor are capped to a valid value.
1022    /// Scrolls the cursor to a visible position.
1023    #[inline]
1024    pub fn set_selection(
1025        &mut self,
1026        anchor: impl Into<TextPosition>,
1027        cursor: impl Into<TextPosition>,
1028    ) -> bool {
1029        self.scroll_cursor_to_visible();
1030        self.value.set_selection(anchor.into(), cursor.into())
1031    }
1032
1033    /// Select all.
1034    /// Scrolls the cursor to a visible position.
1035    #[inline]
1036    pub fn select_all(&mut self) -> bool {
1037        self.scroll_cursor_to_visible();
1038        self.value.select_all()
1039    }
1040
1041    /// Selected text.
1042    #[inline]
1043    pub fn selected_text(&self) -> Cow<'_, str> {
1044        self.value
1045            .str_slice(self.value.selection())
1046            .expect("valid_selection")
1047    }
1048}
1049
1050impl TextAreaState {
1051    /// Empty.
1052    #[inline]
1053    pub fn is_empty(&self) -> bool {
1054        self.value.is_empty()
1055    }
1056
1057    /// Access the underlying rope.
1058    #[inline]
1059    pub fn rope(&self) -> &Rope {
1060        self.value.text().rope()
1061    }
1062
1063    /// Copy of the text-value.
1064    #[inline]
1065    pub fn text(&self) -> String {
1066        self.value.text().string()
1067    }
1068
1069    /// Text slice as `Cow<str>`. Uses a byte range.
1070    ///
1071    /// Panics for an invalid range.
1072    #[inline]
1073    pub fn str_slice_byte(&self, range: Range<usize>) -> Cow<'_, str> {
1074        self.value.str_slice_byte(range).expect("valid_range")
1075    }
1076
1077    /// Text slice as `Cow<str>`. Uses a byte range.
1078    #[inline]
1079    pub fn try_str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
1080        self.value.str_slice_byte(range)
1081    }
1082
1083    /// Text slice as `Cow<str>`
1084    ///
1085    /// Panics for an invalid range.
1086    #[inline]
1087    pub fn str_slice(&self, range: impl Into<TextRange>) -> Cow<'_, str> {
1088        self.value.str_slice(range.into()).expect("valid_range")
1089    }
1090
1091    /// Text slice as `Cow<str>`
1092    #[inline]
1093    pub fn try_str_slice(&self, range: impl Into<TextRange>) -> Result<Cow<'_, str>, TextError> {
1094        self.value.str_slice(range.into())
1095    }
1096
1097    /// Line count.
1098    #[inline]
1099    pub fn len_lines(&self) -> upos_type {
1100        self.value.len_lines()
1101    }
1102
1103    /// Line width as grapheme count.
1104    ///
1105    /// Panics for an invalid row.
1106    #[inline]
1107    pub fn line_width(&self, row: upos_type) -> upos_type {
1108        self.try_line_width(row).expect("valid_row")
1109    }
1110
1111    /// Line width as grapheme count.
1112    #[inline]
1113    pub fn try_line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
1114        self.value.line_width(row)
1115    }
1116
1117    /// Line as `Cow<str>`.
1118    /// This contains the \n at the end.
1119    ///
1120    /// Panics for an invalid row.
1121    #[inline]
1122    pub fn line_at(&self, row: upos_type) -> Cow<'_, str> {
1123        self.value.line_at(row).expect("valid_row")
1124    }
1125
1126    /// Line as `Cow<str>`.
1127    /// This contains the \n at the end.
1128    #[inline]
1129    pub fn try_line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
1130        self.value.line_at(row)
1131    }
1132
1133    /// Iterate over text-lines, starting at offset.
1134    ///
1135    /// Panics for an invalid row.
1136    #[inline]
1137    pub fn lines_at(&self, row: upos_type) -> impl Iterator<Item = Cow<'_, str>> {
1138        self.value.lines_at(row).expect("valid_row")
1139    }
1140
1141    /// Iterate over text-lines, starting at offset.
1142    #[inline]
1143    pub fn try_lines_at(
1144        &self,
1145        row: upos_type,
1146    ) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
1147        self.value.lines_at(row)
1148    }
1149
1150    /// Iterator for the glyphs of the lines in range.
1151    /// Glyphs here a grapheme + display length.
1152    #[inline]
1153    #[allow(deprecated)]
1154    #[deprecated(since = "1.1.0", note = "discontinued api")]
1155    pub fn glyphs(
1156        &self,
1157        rows: Range<upos_type>,
1158        screen_offset: u16,
1159        screen_width: u16,
1160    ) -> impl Iterator<Item = Glyph<'_>> {
1161        self.value
1162            .glyphs(rows, screen_offset, screen_width)
1163            .expect("valid_rows")
1164    }
1165
1166    /// Iterator for the glyphs of the lines in range.
1167    /// Glyphs here a grapheme + display length.
1168    #[inline]
1169    #[allow(deprecated)]
1170    #[deprecated(since = "1.1.0", note = "discontinued api")]
1171    pub fn try_glyphs(
1172        &self,
1173        rows: Range<upos_type>,
1174        screen_offset: u16,
1175        screen_width: u16,
1176    ) -> Result<impl Iterator<Item = Glyph<'_>>, TextError> {
1177        self.value.glyphs(rows, screen_offset, screen_width)
1178    }
1179
1180    /// Grapheme iterator for a given line.
1181    /// This contains the \n at the end.
1182    ///
1183    /// Panics for an invalid row.
1184    #[inline]
1185    pub fn line_graphemes(&self, row: upos_type) -> impl Iterator<Item = Grapheme<'_>> {
1186        self.value.line_graphemes(row).expect("valid_row")
1187    }
1188
1189    /// Grapheme iterator for a given line.
1190    /// This contains the \n at the end.
1191    #[inline]
1192    pub fn try_line_graphemes(
1193        &self,
1194        row: upos_type,
1195    ) -> Result<impl Iterator<Item = Grapheme<'_>>, TextError> {
1196        self.value.line_graphemes(row)
1197    }
1198
1199    /// Get a cursor over all the text with the current position set at pos.
1200    ///
1201    /// Panics for an invalid pos.
1202    #[inline]
1203    pub fn text_graphemes(&self, pos: TextPosition) -> impl Cursor<Item = Grapheme<'_>> {
1204        self.value.text_graphemes(pos).expect("valid_pos")
1205    }
1206
1207    /// Get a cursor over all the text with the current position set at pos.
1208    #[inline]
1209    pub fn try_text_graphemes(
1210        &self,
1211        pos: TextPosition,
1212    ) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
1213        self.value.text_graphemes(pos)
1214    }
1215
1216    /// Get a cursor over the text-range the current position set at pos.
1217    ///
1218    /// Panics for an invalid pos.
1219    #[inline]
1220    pub fn graphemes(
1221        &self,
1222        range: TextRange,
1223        pos: TextPosition,
1224    ) -> impl Cursor<Item = Grapheme<'_>> {
1225        self.value.graphemes(range, pos).expect("valid_args")
1226    }
1227
1228    /// Get a cursor over the text-range the current position set at pos.
1229    #[inline]
1230    pub fn try_graphemes(
1231        &self,
1232        range: TextRange,
1233        pos: TextPosition,
1234    ) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
1235        self.value.graphemes(range, pos)
1236    }
1237
1238    /// Grapheme position to byte position.
1239    /// This is the (start,end) position of the single grapheme after pos.
1240    ///
1241    /// Panics for an invalid pos.
1242    #[inline]
1243    pub fn byte_at(&self, pos: TextPosition) -> Range<usize> {
1244        self.value.byte_at(pos).expect("valid_pos")
1245    }
1246
1247    /// Grapheme position to byte position.
1248    /// This is the (start,end) position of the single grapheme after pos.
1249    #[inline]
1250    pub fn try_byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
1251        self.value.byte_at(pos)
1252    }
1253
1254    /// Grapheme range to byte range.
1255    ///
1256    /// Panics for an invalid range.
1257    #[inline]
1258    pub fn bytes_at_range(&self, range: TextRange) -> Range<usize> {
1259        self.value.bytes_at_range(range).expect("valid_range")
1260    }
1261
1262    /// Grapheme range to byte range.
1263    #[inline]
1264    pub fn try_bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
1265        self.value.bytes_at_range(range)
1266    }
1267
1268    /// Byte position to grapheme position.
1269    /// Returns the position that contains the given byte index.
1270    ///
1271    /// Panics for an invalid byte pos.
1272    #[inline]
1273    pub fn byte_pos(&self, byte: usize) -> TextPosition {
1274        self.value.byte_pos(byte).expect("valid_pos")
1275    }
1276
1277    /// Byte position to grapheme position.
1278    /// Returns the position that contains the given byte index.
1279    #[inline]
1280    pub fn try_byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
1281        self.value.byte_pos(byte)
1282    }
1283
1284    /// Byte range to grapheme range.
1285    ///
1286    /// Panics for an invalid range.
1287    #[inline]
1288    pub fn byte_range(&self, bytes: Range<usize>) -> TextRange {
1289        self.value.byte_range(bytes).expect("valid_range")
1290    }
1291
1292    /// Byte range to grapheme range.
1293    #[inline]
1294    pub fn try_byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
1295        self.value.byte_range(bytes)
1296    }
1297}
1298
1299impl TextAreaState {
1300    /// Clear everything.
1301    #[inline]
1302    pub fn clear(&mut self) -> bool {
1303        if !self.is_empty() {
1304            self.value.clear();
1305            true
1306        } else {
1307            false
1308        }
1309    }
1310
1311    /// Set the text value.
1312    ///
1313    /// Resets all internal state.
1314    #[inline]
1315    pub fn set_text<S: AsRef<str>>(&mut self, s: S) {
1316        self.scroll_to_cursor = false;
1317        self.vscroll.set_offset(0);
1318        self.hscroll.set_offset(0);
1319        self.set_sub_row_offset(0);
1320        self.set_move_col(None);
1321
1322        self.value.set_text(TextRope::new_text(s.as_ref()));
1323    }
1324
1325    /// Set the text value as a Rope.
1326    /// Resets all internal state.
1327    #[inline]
1328    pub fn set_rope(&mut self, r: Rope) {
1329        self.scroll_to_cursor = false;
1330        self.vscroll.set_offset(0);
1331        self.hscroll.set_offset(0);
1332        self.set_sub_row_offset(0);
1333        self.set_move_col(None);
1334
1335        self.value.set_text(TextRope::new_rope(r));
1336    }
1337
1338    /// Insert a character at the cursor position.
1339    /// Removes the selection and inserts the char.
1340    ///
1341    /// You can insert a tab with this. But it will not
1342    /// indent the current selection. It will expand
1343    /// the tab though. Use insert_tab() for this.
1344    ///
1345    /// You can insert a new-line with this. But it will
1346    /// not do an auto-indent.
1347    /// Use insert_new_line() for this.
1348    pub fn insert_char(&mut self, c: char) -> bool {
1349        let mut insert = true;
1350        if self.has_selection() {
1351            if self.auto_quote
1352                && (c == '\''
1353                    || c == '"'
1354                    || c == '`'
1355                    || c == '<'
1356                    || c == '['
1357                    || c == '('
1358                    || c == '{')
1359            {
1360                self.value
1361                    .insert_quotes(self.selection(), c)
1362                    .expect("valid_selection");
1363                insert = false;
1364            } else {
1365                self.value
1366                    .remove_str_range(self.selection())
1367                    .expect("valid_selection");
1368            }
1369        }
1370
1371        if insert {
1372            if c == '\n' {
1373                self.value
1374                    .insert_newline(self.cursor())
1375                    .expect("valid_cursor");
1376            } else if c == '\t' {
1377                self.value.insert_tab(self.cursor()).expect("valid_cursor");
1378            } else {
1379                self.value
1380                    .insert_char(self.cursor(), c)
1381                    .expect("valid_cursor");
1382            }
1383        }
1384
1385        self.scroll_cursor_to_visible();
1386
1387        true
1388    }
1389
1390    /// Inserts tab at the current position. This respects the
1391    /// tab-width set.
1392    ///
1393    /// If there is a text-selection the text-rows will be indented instead.
1394    /// This can be deactivated with auto_indent=false.
1395    pub fn insert_tab(&mut self) -> bool {
1396        if self.has_selection() {
1397            if self.auto_indent {
1398                let sel = self.selection();
1399                let indent = " ".repeat(self.tab_width() as usize);
1400
1401                self.value.begin_undo_seq();
1402                for r in sel.start.y..=sel.end.y {
1403                    self.value
1404                        .insert_str(TextPosition::new(0, r), &indent)
1405                        .expect("valid_row");
1406                }
1407                self.value.end_undo_seq();
1408
1409                true
1410            } else {
1411                false
1412            }
1413        } else {
1414            self.value.insert_tab(self.cursor()).expect("valid_cursor");
1415            self.scroll_cursor_to_visible();
1416
1417            true
1418        }
1419    }
1420
1421    /// Dedent the selected text by tab-width. If there is no
1422    /// selection this does nothing.
1423    ///
1424    /// This can be deactivated with auto_indent=false.
1425    pub fn insert_backtab(&mut self) -> bool {
1426        if self.has_selection() {
1427            let sel = self.selection();
1428
1429            self.value.begin_undo_seq();
1430            for r in sel.start.y..=sel.end.y {
1431                let mut idx = 0;
1432                let g_it = self
1433                    .value
1434                    .graphemes(TextRange::new((0, r), (0, r + 1)), TextPosition::new(0, r))
1435                    .expect("valid_range")
1436                    .take(self.tab_width() as usize);
1437                for g in g_it {
1438                    if g != " " && g != "\t" {
1439                        break;
1440                    }
1441                    idx += 1;
1442                }
1443
1444                self.value
1445                    .remove_str_range(TextRange::new((0, r), (idx, r)))
1446                    .expect("valid_range");
1447            }
1448            self.value.end_undo_seq();
1449
1450            true
1451        } else {
1452            false
1453        }
1454    }
1455
1456    /// Insert text at the cursor position.
1457    /// Removes the selection and inserts the text.
1458    pub fn insert_str(&mut self, t: impl AsRef<str>) -> bool {
1459        let t = t.as_ref();
1460        if self.has_selection() {
1461            self.value
1462                .remove_str_range(self.selection())
1463                .expect("valid_selection");
1464        }
1465        self.value
1466            .insert_str(self.cursor(), t)
1467            .expect("valid_cursor");
1468        self.scroll_cursor_to_visible();
1469        true
1470    }
1471
1472    /// Insert a line break at the cursor position.
1473    ///
1474    /// If auto_indent is set the new line starts with the same
1475    /// indent as the current.
1476    pub fn insert_newline(&mut self) -> bool {
1477        if self.has_selection() {
1478            self.value
1479                .remove_str_range(self.selection())
1480                .expect("valid_selection");
1481        }
1482        self.value
1483            .insert_newline(self.cursor())
1484            .expect("valid_cursor");
1485
1486        // insert leading spaces
1487        if self.auto_indent {
1488            let cursor = self.cursor();
1489            if cursor.y > 0 {
1490                let mut blanks = String::new();
1491                for g in self.line_graphemes(cursor.y - 1) {
1492                    if g == " " || g == "\t" {
1493                        blanks.push_str(g.grapheme());
1494                    } else {
1495                        break;
1496                    }
1497                }
1498                if !blanks.is_empty() {
1499                    self.value
1500                        .insert_str(cursor, &blanks)
1501                        .expect("valid_cursor");
1502                }
1503            }
1504        }
1505
1506        self.scroll_cursor_to_visible();
1507        true
1508    }
1509
1510    /// Deletes the given range.
1511    ///
1512    /// Panics for an invalid range.
1513    #[inline]
1514    pub fn delete_range(&mut self, range: impl Into<TextRange>) -> bool {
1515        self.try_delete_range(range).expect("valid_range")
1516    }
1517
1518    /// Deletes the given range.
1519    #[inline]
1520    pub fn try_delete_range(&mut self, range: impl Into<TextRange>) -> Result<bool, TextError> {
1521        let range = range.into();
1522        if !range.is_empty() {
1523            self.value.remove_str_range(range)?;
1524            self.scroll_cursor_to_visible();
1525            Ok(true)
1526        } else {
1527            Ok(false)
1528        }
1529    }
1530}
1531
1532impl TextAreaState {
1533    /// Duplicates the selection or the current line.
1534    /// Returns true if there was any real change.
1535    pub fn duplicate_text(&mut self) -> bool {
1536        if self.has_selection() {
1537            let sel_range = self.selection();
1538            if !sel_range.is_empty() {
1539                let v = self.str_slice(sel_range).to_string();
1540                self.value
1541                    .insert_str(sel_range.end, &v)
1542                    .expect("valid_selection");
1543                true
1544            } else {
1545                false
1546            }
1547        } else {
1548            let pos = self.cursor();
1549            let row_range = TextRange::new((0, pos.y), (0, pos.y + 1));
1550            let v = self.str_slice(row_range).to_string();
1551            self.value
1552                .insert_str(row_range.start, &v)
1553                .expect("valid_cursor");
1554            true
1555        }
1556    }
1557
1558    /// Deletes the current line.
1559    /// Returns true if there was any real change.
1560    pub fn delete_line(&mut self) -> bool {
1561        let pos = self.cursor();
1562        if pos.y + 1 < self.len_lines() {
1563            self.delete_range(TextRange::new((0, pos.y), (0, pos.y + 1)))
1564        } else {
1565            let width = self.line_width(pos.y);
1566            self.delete_range(TextRange::new((0, pos.y), (width, pos.y)))
1567        }
1568    }
1569
1570    /// Deletes the next char or the current selection.
1571    /// Returns true if there was any real change.
1572    pub fn delete_next_char(&mut self) -> bool {
1573        if self.has_selection() {
1574            self.delete_range(self.selection())
1575        } else {
1576            let r = self
1577                .value
1578                .remove_next_char(self.cursor())
1579                .expect("valid_cursor");
1580            self.scroll_cursor_to_visible();
1581            r
1582        }
1583    }
1584
1585    /// Deletes the previous char or the selection.
1586    /// Returns true if there was any real change.
1587    pub fn delete_prev_char(&mut self) -> bool {
1588        if self.has_selection() {
1589            self.delete_range(self.selection())
1590        } else {
1591            let r = self
1592                .value
1593                .remove_prev_char(self.cursor())
1594                .expect("valid_cursor");
1595            self.scroll_cursor_to_visible();
1596            r
1597        }
1598    }
1599
1600    /// Find the start of the next word. If the position is at the start
1601    /// or inside a word, the same position is returned.
1602    ///
1603    /// Panics for an invalid pos.
1604    #[inline]
1605    pub fn next_word_start(&self, pos: impl Into<TextPosition>) -> TextPosition {
1606        self.value.next_word_start(pos.into()).expect("valid_pos")
1607    }
1608
1609    /// Find the start of the next word. If the position is at the start
1610    /// or inside a word, the same position is returned.
1611    #[inline]
1612    pub fn try_next_word_start(
1613        &self,
1614        pos: impl Into<TextPosition>,
1615    ) -> Result<TextPosition, TextError> {
1616        self.value.next_word_start(pos.into())
1617    }
1618
1619    /// Find the end of the next word. Skips whitespace first, then goes on
1620    /// until it finds the next whitespace.
1621    ///
1622    /// Panics for an invalid pos.
1623    #[inline]
1624    pub fn next_word_end(&self, pos: impl Into<TextPosition>) -> TextPosition {
1625        self.value.next_word_end(pos.into()).expect("valid_pos")
1626    }
1627
1628    /// Find the end of the next word. Skips whitespace first, then goes on
1629    /// until it finds the next whitespace.
1630    #[inline]
1631    pub fn try_next_word_end(
1632        &self,
1633        pos: impl Into<TextPosition>,
1634    ) -> Result<TextPosition, TextError> {
1635        self.value.next_word_end(pos.into())
1636    }
1637
1638    /// Find the start of the prev word. Skips whitespace first, then goes on
1639    /// until it finds the next whitespace.
1640    ///
1641    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1642    /// both return start<=end!
1643    ///
1644    /// Panics for an invalid range.
1645    #[inline]
1646    pub fn prev_word_start(&self, pos: impl Into<TextPosition>) -> TextPosition {
1647        self.value.prev_word_start(pos.into()).expect("valid_pos")
1648    }
1649
1650    /// Find the start of the prev word. Skips whitespace first, then goes on
1651    /// until it finds the next whitespace.
1652    ///
1653    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1654    /// both return start<=end!
1655    #[inline]
1656    pub fn try_prev_word_start(
1657        &self,
1658        pos: impl Into<TextPosition>,
1659    ) -> Result<TextPosition, TextError> {
1660        self.value.prev_word_start(pos.into())
1661    }
1662
1663    /// Find the end of the previous word. Word is everything that is not whitespace.
1664    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1665    /// both return start<=end!
1666    ///
1667    /// Panics for an invalid range.
1668    #[inline]
1669    pub fn prev_word_end(&self, pos: impl Into<TextPosition>) -> TextPosition {
1670        self.value.prev_word_end(pos.into()).expect("valid_pos")
1671    }
1672
1673    /// Find the end of the previous word. Word is everything that is not whitespace.
1674    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1675    /// both return start<=end!
1676    #[inline]
1677    pub fn try_prev_word_end(
1678        &self,
1679        pos: impl Into<TextPosition>,
1680    ) -> Result<TextPosition, TextError> {
1681        self.value.prev_word_end(pos.into())
1682    }
1683
1684    /// Is the position at a word boundary?
1685    ///
1686    /// Panics for an invalid range.
1687    #[inline]
1688    pub fn is_word_boundary(&self, pos: impl Into<TextPosition>) -> bool {
1689        self.value.is_word_boundary(pos.into()).expect("valid_pos")
1690    }
1691
1692    /// Is the position at a word boundary?
1693    #[inline]
1694    pub fn try_is_word_boundary(&self, pos: impl Into<TextPosition>) -> Result<bool, TextError> {
1695        self.value.is_word_boundary(pos.into())
1696    }
1697
1698    /// Find the start of the word at pos.
1699    /// Returns pos if the position is not inside a word.
1700    ///
1701    /// Panics for an invalid range.
1702    #[inline]
1703    pub fn word_start(&self, pos: impl Into<TextPosition>) -> TextPosition {
1704        self.value.word_start(pos.into()).expect("valid_pos")
1705    }
1706
1707    /// Find the start of the word at pos.
1708    /// Returns pos if the position is not inside a word.
1709    #[inline]
1710    pub fn try_word_start(&self, pos: impl Into<TextPosition>) -> Result<TextPosition, TextError> {
1711        self.value.word_start(pos.into())
1712    }
1713
1714    /// Find the end of the word at pos.
1715    /// Returns pos if the position is not inside a word.
1716    ///
1717    /// Panics for an invalid range.
1718    #[inline]
1719    pub fn word_end(&self, pos: impl Into<TextPosition>) -> TextPosition {
1720        self.value.word_end(pos.into()).expect("valid_pos")
1721    }
1722
1723    /// Find the end of the word at pos.
1724    /// Returns pos if the position is not inside a word.
1725    #[inline]
1726    pub fn try_word_end(&self, pos: impl Into<TextPosition>) -> Result<TextPosition, TextError> {
1727        self.value.word_end(pos.into())
1728    }
1729
1730    /// Delete the next word. This alternates deleting the whitespace between words and
1731    /// the words themselves.
1732    ///
1733    /// If there is a selection, removes only the selected text.
1734    pub fn delete_next_word(&mut self) -> bool {
1735        if self.has_selection() {
1736            self.delete_range(self.selection())
1737        } else {
1738            let cursor = self.cursor();
1739
1740            let start = self.next_word_start(cursor);
1741            if start != cursor {
1742                self.delete_range(cursor..start)
1743            } else {
1744                let end = self.next_word_end(cursor);
1745                self.delete_range(cursor..end)
1746            }
1747        }
1748    }
1749
1750    /// Deletes the previous word. This alternates deleting the whitespace
1751    /// between words and the words themselves.
1752    ///
1753    /// If there is a selection, removes only the selected text.
1754    pub fn delete_prev_word(&mut self) -> bool {
1755        if self.has_selection() {
1756            self.delete_range(self.selection())
1757        } else {
1758            let cursor = self.cursor();
1759
1760            // delete to beginning of line?
1761            let till_line_start = if cursor.x != 0 {
1762                self.graphemes(TextRange::new((0, cursor.y), cursor), cursor)
1763                    .rev_cursor()
1764                    .all(|v| v.is_whitespace())
1765            } else {
1766                false
1767            };
1768
1769            if till_line_start {
1770                self.delete_range(TextRange::new((0, cursor.y), cursor))
1771            } else {
1772                let end = self.prev_word_end(cursor);
1773                if end != cursor {
1774                    self.delete_range(end..cursor)
1775                } else {
1776                    let start = self.prev_word_start(cursor);
1777                    self.delete_range(start..cursor)
1778                }
1779            }
1780        }
1781    }
1782
1783    /// Move the cursor left. Scrolls the cursor to visible.
1784    /// Returns true if there was any real change.
1785    pub fn move_left(&mut self, n: u16, extend_selection: bool) -> bool {
1786        let mut cursor = self.cursor();
1787        if cursor.x == 0 {
1788            if cursor.y > 0 {
1789                cursor.y = cursor.y.saturating_sub(1);
1790                cursor.x = self.line_width(cursor.y);
1791            }
1792        } else {
1793            cursor.x = cursor.x.saturating_sub(n as upos_type);
1794        }
1795
1796        if let Some(scr_cursor) = self.pos_to_relative_screen(cursor) {
1797            self.set_move_col(Some(scr_cursor.0));
1798        }
1799
1800        self.set_cursor(cursor, extend_selection)
1801    }
1802
1803    /// Move the cursor right. Scrolls the cursor to visible.
1804    /// Returns true if there was any real change.
1805    pub fn move_right(&mut self, n: u16, extend_selection: bool) -> bool {
1806        let mut cursor = self.cursor();
1807        let c_line_width = self.line_width(cursor.y);
1808        if cursor.x == c_line_width {
1809            if cursor.y + 1 < self.len_lines() {
1810                cursor.y += 1;
1811                cursor.x = 0;
1812            }
1813        } else {
1814            cursor.x = min(cursor.x + n as upos_type, c_line_width)
1815        }
1816
1817        if let Some(scr_cursor) = self.pos_to_relative_screen(cursor) {
1818            self.set_move_col(Some(scr_cursor.0));
1819        }
1820        self.set_cursor(cursor, extend_selection)
1821    }
1822
1823    /// Move the cursor up. Scrolls the cursor to visible.
1824    /// Returns true if there was any real change.
1825    pub fn move_up(&mut self, n: u16, extend_selection: bool) -> bool {
1826        let cursor = self.cursor();
1827        if let Some(mut scr_cursor) = self.pos_to_relative_screen(cursor) {
1828            if let Some(move_col) = self.move_col() {
1829                scr_cursor.0 = move_col;
1830            }
1831            scr_cursor.1 -= n as i16;
1832
1833            if let Some(new_cursor) = self.relative_screen_to_pos(scr_cursor) {
1834                self.set_cursor(new_cursor, extend_selection)
1835            } else {
1836                self.scroll_cursor_to_visible();
1837                true
1838            }
1839        } else {
1840            self.scroll_cursor_to_visible();
1841            true
1842        }
1843    }
1844
1845    /// Move the cursor down. Scrolls the cursor to visible.
1846    /// Returns true if there was any real change.
1847    pub fn move_down(&mut self, n: u16, extend_selection: bool) -> bool {
1848        let cursor = self.cursor();
1849        if let Some(mut scr_cursor) = self.pos_to_relative_screen(cursor) {
1850            if let Some(move_col) = self.move_col() {
1851                scr_cursor.0 = move_col;
1852            }
1853            scr_cursor.1 += n as i16;
1854
1855            if let Some(new_cursor) = self.relative_screen_to_pos(scr_cursor) {
1856                self.set_cursor(new_cursor, extend_selection)
1857            } else {
1858                self.scroll_cursor_to_visible();
1859                true
1860            }
1861        } else {
1862            self.scroll_cursor_to_visible();
1863            true
1864        }
1865    }
1866
1867    /// Move the cursor to the start of the line.
1868    /// Scrolls the cursor to visible.
1869    /// Returns true if there was any real change.
1870    pub fn move_to_line_start(&mut self, extend_selection: bool) -> bool {
1871        let cursor = self.cursor();
1872
1873        let mut line_start = self.pos_to_line_start(cursor);
1874        for g in self
1875            .glyphs2(
1876                0,
1877                line_start.x,
1878                line_start.y..min(line_start.y + 1, self.len_lines()),
1879            )
1880            .expect("valid-pos")
1881        {
1882            if g.glyph() != " " && g.glyph() != "\t" {
1883                if g.pos().x != cursor.x {
1884                    line_start.x = g.pos().x;
1885                }
1886                break;
1887            }
1888        }
1889
1890        if let Some(scr_pos) = self.pos_to_relative_screen(line_start) {
1891            self.set_move_col(Some(scr_pos.0));
1892        }
1893        self.set_cursor(line_start, extend_selection)
1894    }
1895
1896    /// Move the cursor to the end of the line. Scrolls to visible, if
1897    /// necessary.
1898    /// Returns true if there was any real change.
1899    pub fn move_to_line_end(&mut self, extend_selection: bool) -> bool {
1900        let cursor = self.cursor();
1901        let line_end = self.pos_to_line_end(cursor);
1902        if let Some(scr_pos) = self.pos_to_relative_screen(line_end) {
1903            self.set_move_col(Some(scr_pos.0));
1904        }
1905        self.set_cursor(line_end, extend_selection)
1906    }
1907
1908    /// Move the cursor to the document start.
1909    pub fn move_to_start(&mut self, extend_selection: bool) -> bool {
1910        let cursor = TextPosition::new(0, 0);
1911
1912        self.set_move_col(Some(0));
1913        self.set_cursor(cursor, extend_selection)
1914    }
1915
1916    /// Move the cursor to the document end.
1917    pub fn move_to_end(&mut self, extend_selection: bool) -> bool {
1918        let cursor = TextPosition::new(
1919            self.line_width(self.len_lines().saturating_sub(1)),
1920            self.len_lines().saturating_sub(1),
1921        );
1922
1923        let line_start = self.pos_to_line_start(cursor);
1924        self.set_move_col(Some(0));
1925        self.set_cursor(line_start, extend_selection)
1926    }
1927
1928    /// Move the cursor to the start of the visible area.
1929    pub fn move_to_screen_start(&mut self, extend_selection: bool) -> bool {
1930        let (ox, oy) = self.offset();
1931
1932        let cursor = TextPosition::new(ox as upos_type, oy as upos_type);
1933
1934        self.set_move_col(Some(0));
1935        self.set_cursor(cursor, extend_selection)
1936    }
1937
1938    /// Move the cursor to the end of the visible area.
1939    pub fn move_to_screen_end(&mut self, extend_selection: bool) -> bool {
1940        let scr_end = (0, (self.inner.height as i16).saturating_sub(1));
1941        if let Some(pos) = self.relative_screen_to_pos(scr_end) {
1942            self.set_move_col(Some(0));
1943            self.set_cursor(pos, extend_selection)
1944        } else {
1945            self.scroll_cursor_to_visible();
1946            true
1947        }
1948    }
1949
1950    /// Move the cursor to the next word.
1951    pub fn move_to_next_word(&mut self, extend_selection: bool) -> bool {
1952        let cursor = self.cursor();
1953
1954        let word = self.next_word_end(cursor);
1955
1956        if let Some(scr_pos) = self.pos_to_relative_screen(word) {
1957            self.set_move_col(Some(scr_pos.0));
1958        }
1959        self.set_cursor(word, extend_selection)
1960    }
1961
1962    /// Move the cursor to the previous word.
1963    pub fn move_to_prev_word(&mut self, extend_selection: bool) -> bool {
1964        let cursor = self.cursor();
1965
1966        let word = self.prev_word_start(cursor);
1967
1968        if let Some(scr_pos) = self.pos_to_relative_screen(word) {
1969            self.set_move_col(Some(scr_pos.0));
1970        }
1971        self.set_cursor(word, extend_selection)
1972    }
1973}
1974
1975impl HasScreenCursor for TextAreaState {
1976    /// Cursor position on the screen.
1977    #[allow(clippy::question_mark)]
1978    fn screen_cursor(&self) -> Option<(u16, u16)> {
1979        if self.is_focused() {
1980            if self.has_selection() {
1981                None
1982            } else {
1983                let Some(scr_cursor) = self.screen_cursor else {
1984                    return None;
1985                };
1986
1987                if !(scr_cursor.0 >= self.inner.x
1988                    && scr_cursor.0 <= self.inner.right()
1989                    && scr_cursor.1 >= self.inner.y
1990                    && scr_cursor.1 < self.inner.bottom())
1991                {
1992                    return None;
1993                }
1994                Some(scr_cursor)
1995            }
1996        } else {
1997            None
1998        }
1999    }
2000}
2001
2002impl RelocatableState for TextAreaState {
2003    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
2004        // clip offset for some corrections.
2005        self.dark_offset = relocate_dark_offset(self.inner, shift, clip);
2006        self.area = relocate_area(self.area, shift, clip);
2007        self.inner = relocate_area(self.inner, shift, clip);
2008        if let Some(screen_cursor) = self.screen_cursor {
2009            self.screen_cursor = relocate_pos_tuple(screen_cursor, shift, clip);
2010        }
2011    }
2012}
2013
2014impl TextAreaState {
2015    fn text_wrap_2(&self, shift_left: upos_type) -> (TextWrap2, upos_type, upos_type, upos_type) {
2016        match self.text_wrap {
2017            TextWrap::Shift => (
2018                TextWrap2::Shift,
2019                shift_left,
2020                shift_left + self.rendered.width as upos_type,
2021                shift_left + self.rendered.width as upos_type,
2022            ),
2023            TextWrap::Hard => (
2024                TextWrap2::Hard,
2025                0,
2026                self.rendered.width as upos_type,
2027                self.rendered.width as upos_type,
2028            ),
2029            TextWrap::Word(margin) => (
2030                TextWrap2::Word,
2031                0,
2032                self.rendered.width as upos_type,
2033                self.rendered.width.saturating_sub(margin) as upos_type,
2034            ),
2035        }
2036    }
2037
2038    /// Fill the cache for the given rows.
2039    /// Build up the complete information for the given rows.
2040    fn fill_cache(
2041        &self,
2042        shift_left: upos_type,
2043        sub_row_offset: upos_type,
2044        rows: Range<upos_type>,
2045    ) -> Result<(), TextError> {
2046        let (text_wrap, left_margin, right_margin, word_margin) = self.text_wrap_2(shift_left);
2047        self.value.fill_cache(
2048            self.rendered,
2049            sub_row_offset,
2050            rows,
2051            text_wrap,
2052            self.wrap_ctrl() | self.show_ctrl(),
2053            left_margin,
2054            right_margin,
2055            word_margin,
2056        )
2057    }
2058
2059    fn glyphs2(
2060        &self,
2061        shift_left: upos_type,
2062        sub_row_offset: upos_type,
2063        rows: Range<upos_type>,
2064    ) -> Result<GlyphIter2<<TextRope as TextStore>::GraphemeIter<'_>>, TextError> {
2065        let (text_wrap, left_margin, right_margin, word_margin) = self.text_wrap_2(shift_left);
2066        self.value.glyphs2(
2067            self.rendered,
2068            sub_row_offset,
2069            rows,
2070            text_wrap,
2071            self.wrap_ctrl() | self.show_ctrl(),
2072            left_margin,
2073            right_margin,
2074            word_margin,
2075        )
2076    }
2077
2078    /// Find the text-position for an absolute screen-position.
2079    pub fn screen_to_pos(&self, scr_pos: (u16, u16)) -> Option<TextPosition> {
2080        let scr_pos = (
2081            scr_pos.0 as i16 - self.inner.x as i16,
2082            scr_pos.1 as i16 - self.inner.y as i16,
2083        );
2084        self.relative_screen_to_pos(scr_pos)
2085    }
2086
2087    /// Find the absolute screen-position for a text-position
2088    pub fn pos_to_screen(&self, pos: TextPosition) -> Option<(u16, u16)> {
2089        let scr_pos = self.pos_to_relative_screen(pos)?;
2090        if scr_pos.0 + self.inner.x as i16 > 0 && scr_pos.1 + self.inner.y as i16 > 0 {
2091            Some((
2092                (scr_pos.0 + self.inner.x as i16) as u16,
2093                (scr_pos.1 + self.inner.y as i16) as u16,
2094            ))
2095        } else {
2096            None
2097        }
2098    }
2099
2100    /// Return the starting position for the visible line containing the given position.
2101    pub fn pos_to_line_start(&self, pos: TextPosition) -> TextPosition {
2102        match self.text_wrap {
2103            TextWrap::Shift => {
2104                //
2105                TextPosition::new(0, pos.y)
2106            }
2107            TextWrap::Hard | TextWrap::Word(_) => {
2108                self.fill_cache(0, 0, pos.y..min(pos.y + 1, self.len_lines()))
2109                    .expect("valid-row");
2110
2111                let mut start_pos = TextPosition::new(0, pos.y);
2112                for (break_pos, _) in self.value.cache().line_break.borrow().range(
2113                    TextPosition::new(0, pos.y)
2114                        ..TextPosition::new(0, min(pos.y + 1, self.len_lines())),
2115                ) {
2116                    if pos >= start_pos && &pos <= break_pos {
2117                        break;
2118                    }
2119                    start_pos = TextPosition::new(break_pos.x + 1, break_pos.y);
2120                }
2121
2122                start_pos
2123            }
2124        }
2125    }
2126
2127    /// Return the end position for the visible line containing the given position.
2128    pub fn pos_to_line_end(&self, pos: TextPosition) -> TextPosition {
2129        self.fill_cache(0, 0, pos.y..min(pos.y + 1, self.len_lines()))
2130            .expect("valid-row");
2131
2132        let mut end_pos = TextPosition::new(0, pos.y);
2133        for (break_pos, _) in self
2134            .value
2135            .cache()
2136            .line_break
2137            .borrow()
2138            .range(TextPosition::new(0, pos.y)..TextPosition::new(0, pos.y + 1))
2139        {
2140            if pos >= end_pos && &pos <= break_pos {
2141                end_pos = *break_pos;
2142                break;
2143            }
2144            end_pos = TextPosition::new(break_pos.x + 1, break_pos.y);
2145        }
2146
2147        end_pos
2148    }
2149
2150    fn scroll_to_cursor(&mut self) {
2151        let cursor = self.cursor();
2152
2153        let nox;
2154        let noy;
2155        let mut nsub_row_offset;
2156        match self.text_wrap {
2157            TextWrap::Shift => {
2158                let (ox, _, oy) = self.clean_offset();
2159
2160                let height = self.rendered.height as upos_type;
2161                let width = self.rendered.width as upos_type;
2162                let width = if self.show_ctrl() || self.wrap_ctrl() {
2163                    width.saturating_sub(1)
2164                } else {
2165                    width
2166                };
2167
2168                noy = if cursor.y < oy {
2169                    cursor.y
2170                } else if cursor.y >= oy + height {
2171                    cursor.y.saturating_sub(height) + 1
2172                } else {
2173                    oy
2174                };
2175
2176                nox = if cursor.x < ox {
2177                    cursor.x
2178                } else if cursor.x >= ox + width {
2179                    cursor.x.saturating_sub(width) + 1
2180                } else {
2181                    ox
2182                };
2183
2184                nsub_row_offset = 0;
2185            }
2186            TextWrap::Hard | TextWrap::Word(_) => {
2187                let (ox, sub_row_offset, oy) = self.clean_offset();
2188
2189                // try to find the cursor on the visible screen.
2190                // scans one more extra screen-height afterwards.
2191                'f: {
2192                    let mut sub_row_offsets = Vec::new();
2193                    for g in self
2194                        .glyphs2(
2195                            0,
2196                            sub_row_offset,
2197                            oy..min(oy + 2 * self.rendered.height as upos_type, self.len_lines()),
2198                        )
2199                        .expect("valid_offset")
2200                    {
2201                        if g.screen_pos().0 == 0 {
2202                            sub_row_offsets.push((g.pos().x, g.pos().y));
2203                        }
2204                        if g.screen_pos().1 >= 2 * self.rendered.height {
2205                            break;
2206                        }
2207                        if g.pos() == cursor {
2208                            if g.screen_pos().1 >= self.rendered.height {
2209                                (nsub_row_offset, noy) = sub_row_offsets
2210                                    [(g.screen_pos().1 - self.rendered.height + 1) as usize];
2211                                nox = ox;
2212                            } else {
2213                                nsub_row_offset = sub_row_offset;
2214                                nox = ox;
2215                                noy = oy;
2216                            }
2217
2218                            break 'f;
2219                        }
2220                    }
2221
2222                    // cursor is not on the screen.
2223                    //
2224                    // find the correct line-start for the cursor.
2225                    // set the cursor as the new offset.
2226                    //
2227                    nsub_row_offset = 0;
2228                    for g in self
2229                        .glyphs2(0, 0, cursor.y..min(cursor.y + 1, self.len_lines()))
2230                        .expect("valid_offset")
2231                    {
2232                        if g.screen_pos().0 == 0 {
2233                            nsub_row_offset = g.pos().x;
2234                        }
2235                        if g.pos() == cursor {
2236                            break; // found
2237                        }
2238                    }
2239
2240                    nox = 0;
2241                    noy = cursor.y;
2242                }
2243            }
2244        }
2245
2246        self.set_offset((nox as usize, noy as usize));
2247        self.set_sub_row_offset(nsub_row_offset);
2248    }
2249
2250    /// Return the screen_position for the given text position
2251    /// relative to the origin of the widget.
2252    ///
2253    /// This may be outside the visible area, if the text-area
2254    /// has been relocated. It may even be outside the screen,
2255    /// so this returns an (i16, i16) as an absolute screen position.
2256    ///
2257    /// If the text-position is outside the rendered area,
2258    /// this will return None.
2259    #[allow(clippy::explicit_counter_loop)]
2260    pub fn pos_to_relative_screen(&self, pos: TextPosition) -> Option<(i16, i16)> {
2261        match self.text_wrap {
2262            TextWrap::Shift => {
2263                let (ox, _, oy) = self.clean_offset();
2264
2265                if oy > self.len_lines() {
2266                    return None;
2267                }
2268
2269                if pos.y < oy {
2270                    return None;
2271                }
2272                if pos.y > self.len_lines() {
2273                    return None;
2274                }
2275                if pos.y - oy >= self.rendered.height as u32 {
2276                    return None;
2277                }
2278
2279                let screen_y = (pos.y - oy) as u16;
2280
2281                let screen_x = 'f: {
2282                    for g in self
2283                        .glyphs2(ox, 0, pos.y..min(pos.y + 1, self.len_lines()))
2284                        .expect("valid-row")
2285                    {
2286                        if g.pos().x == pos.x {
2287                            break 'f g.screen_pos().0;
2288                        } else if g.line_break() {
2289                            break 'f g.screen_pos().0;
2290                        }
2291                    }
2292                    return None;
2293                };
2294                assert!(screen_x <= self.rendered.width);
2295
2296                Some((
2297                    screen_x as i16 - self.dark_offset.0 as i16,
2298                    screen_y as i16 - self.dark_offset.1 as i16,
2299                ))
2300            }
2301            TextWrap::Hard | TextWrap::Word(_) => {
2302                let (_, sub_row_offset, oy) = self.clean_offset();
2303
2304                if oy > self.len_lines() {
2305                    return None;
2306                }
2307                if pos.y < oy {
2308                    return None;
2309                }
2310                if pos.y > self.len_lines() {
2311                    return None;
2312                }
2313
2314                self.fill_cache(
2315                    0,
2316                    sub_row_offset,
2317                    oy..min(oy + self.rendered.height as upos_type, self.len_lines()),
2318                )
2319                .expect("valid-rows");
2320
2321                let (screen_y, start_pos) = 'f: {
2322                    let mut prev_screen_y = 0;
2323                    let mut prev_pos = TextPosition::new(sub_row_offset, oy);
2324                    let mut screen_y = 0;
2325                    let mut start_pos = TextPosition::new(sub_row_offset, oy);
2326
2327                    for (_, cache) in self.value.cache().line_break.borrow().range(
2328                        TextPosition::new(sub_row_offset, oy)
2329                            ..TextPosition::new(
2330                                0,
2331                                min(oy + self.rendered.height as upos_type, self.len_lines()),
2332                            ),
2333                    ) {
2334                        if screen_y >= self.rendered.height {
2335                            return None;
2336                        }
2337                        if pos < cache.start_pos {
2338                            break 'f (screen_y, start_pos);
2339                        }
2340
2341                        prev_pos = start_pos;
2342                        prev_screen_y = screen_y;
2343
2344                        screen_y += 1;
2345                        start_pos = cache.start_pos;
2346                    }
2347
2348                    // very last position on the very last row without a \n
2349                    if pos == start_pos {
2350                        break 'f (prev_screen_y, prev_pos);
2351                    }
2352                    return None;
2353                };
2354
2355                let screen_x = 'f: {
2356                    for g in self
2357                        .glyphs2(
2358                            0,
2359                            start_pos.x,
2360                            start_pos.y..min(start_pos.y + 1, self.len_lines()),
2361                        )
2362                        .expect("valid-row")
2363                    {
2364                        if g.pos().x == pos.x {
2365                            break 'f g.screen_pos().0;
2366                        } else if g.line_break() {
2367                            break 'f g.screen_pos().0;
2368                        }
2369                    }
2370                    return None;
2371                };
2372                assert!(screen_x <= self.rendered.width);
2373
2374                Some((
2375                    screen_x as i16 - self.dark_offset.0 as i16,
2376                    screen_y as i16 - self.dark_offset.1 as i16,
2377                ))
2378            }
2379        }
2380    }
2381
2382    /// Find the text-position for the widget-relative screen-position.
2383    #[allow(clippy::needless_return)]
2384    pub fn relative_screen_to_pos(&self, scr_pos: (i16, i16)) -> Option<TextPosition> {
2385        let scr_pos = (
2386            scr_pos.0 + self.dark_offset.0 as i16,
2387            scr_pos.1 + self.dark_offset.1 as i16,
2388        );
2389
2390        match self.text_wrap {
2391            TextWrap::Shift => {
2392                let (ox, _, oy) = self.clean_offset();
2393
2394                if oy >= self.len_lines() {
2395                    return None;
2396                }
2397                if scr_pos.1 < 0 {
2398                    // before the first visible line. fall back to col 0.
2399                    return Some(TextPosition::new(
2400                        0,
2401                        oy.saturating_add_signed(scr_pos.1 as ipos_type),
2402                    ));
2403                }
2404                if (oy + scr_pos.1 as upos_type) >= self.len_lines() {
2405                    // after the last visible line. fall back to width.
2406                    return Some(TextPosition::new(
2407                        self.line_width(self.len_lines().saturating_sub(1)),
2408                        self.len_lines().saturating_sub(1),
2409                    ));
2410                }
2411
2412                let pos_y = oy + scr_pos.1 as upos_type;
2413
2414                if scr_pos.0 < 0 {
2415                    return Some(TextPosition::new(
2416                        ox.saturating_add_signed(scr_pos.0 as ipos_type),
2417                        pos_y,
2418                    ));
2419                } else if scr_pos.0 as u16 >= self.rendered.width {
2420                    return Some(TextPosition::new(
2421                        min(ox + scr_pos.0 as upos_type, self.line_width(pos_y)),
2422                        pos_y,
2423                    ));
2424                } else {
2425                    for g in self
2426                        .glyphs2(ox, 0, pos_y..min(pos_y + 1, self.len_lines()))
2427                        .expect("valid-position")
2428                    {
2429                        if g.contains_screen_x(scr_pos.0 as u16) {
2430                            return Some(TextPosition::new(g.pos().x, pos_y));
2431                        }
2432                    }
2433                    unreachable!();
2434                }
2435            }
2436            TextWrap::Hard | TextWrap::Word(_) => {
2437                let (_, sub_row_offset, oy) = self.clean_offset();
2438
2439                if oy >= self.len_lines() {
2440                    return None;
2441                }
2442
2443                if scr_pos.1 < 0 {
2444                    // Guess a starting position for an alternate screen that
2445                    // would contain the given screen-position.
2446                    // By locating our actual offset within that screen we can
2447                    // calculate the correct screen-position for that alternate
2448                    // screen. And then find the correct text-position for
2449                    // that again.
2450
2451                    // estimate start row
2452                    let ry = oy.saturating_add_signed(scr_pos.1 as ipos_type - 1);
2453
2454                    // count backwards
2455                    self.fill_cache(0, 0, ry..oy).expect("valid-rows");
2456
2457                    let n_start_pos = 'f: {
2458                        let mut nrows = scr_pos.1.unsigned_abs();
2459                        for (_break_pos, cache) in self
2460                            .value
2461                            .cache()
2462                            .line_break
2463                            .borrow()
2464                            .range(TextPosition::new(0, ry)..TextPosition::new(sub_row_offset, oy))
2465                            .rev()
2466                        {
2467                            if nrows == 0 {
2468                                break 'f cache.start_pos;
2469                            }
2470                            nrows -= 1;
2471                        }
2472                        TextPosition::new(0, 0)
2473                    };
2474
2475                    // find the exact col
2476                    if scr_pos.0 < 0 {
2477                        return Some(n_start_pos);
2478                    }
2479
2480                    for g in self
2481                        .glyphs2(
2482                            0,
2483                            n_start_pos.x,
2484                            n_start_pos.y..min(n_start_pos.y + 1, self.len_lines()),
2485                        )
2486                        .expect("valid-rows")
2487                    {
2488                        if g.contains_screen_x(scr_pos.0 as u16) {
2489                            return Some(g.pos());
2490                        }
2491                    }
2492
2493                    unreachable!("impossible screen-pos");
2494                } else {
2495                    let scr_pos = (max(0, scr_pos.0) as u16, scr_pos.1 as u16);
2496
2497                    // row-0 equals the current offset. done.
2498                    let n_start_pos = if scr_pos.1 == 0 {
2499                        TextPosition::new(sub_row_offset, oy)
2500                    } else {
2501                        // start at the offset and find the screen-position.
2502                        // count backwards
2503                        self.fill_cache(
2504                            0,
2505                            sub_row_offset,
2506                            oy..min(oy + scr_pos.1 as upos_type, self.len_lines()),
2507                        )
2508                        .expect("valid-rows");
2509
2510                        'f: {
2511                            let mut nrows = scr_pos.1 - 1;
2512                            let mut fallback = TextPosition::new(sub_row_offset, oy);
2513                            for (_break_pos, cache) in self.value.cache().line_break.borrow().range(
2514                                TextPosition::new(sub_row_offset, oy)
2515                                    ..TextPosition::new(0, self.len_lines()),
2516                            ) {
2517                                if nrows == 0 {
2518                                    break 'f cache.start_pos;
2519                                }
2520                                fallback = cache.start_pos;
2521                                nrows -= 1;
2522                            }
2523                            fallback
2524                        }
2525                    };
2526
2527                    for g in self
2528                        .glyphs2(
2529                            0,
2530                            n_start_pos.x,
2531                            n_start_pos.y..min(n_start_pos.y + 1, self.len_lines()),
2532                        )
2533                        .expect("valid-rows")
2534                    {
2535                        if g.contains_screen_x(scr_pos.0) {
2536                            return Some(g.pos());
2537                        }
2538                    }
2539                    unreachable!("impossible screen-pos");
2540                }
2541            }
2542        }
2543    }
2544}
2545
2546impl TextAreaState {
2547    /// Converts from a widget relative screen coordinate to a line.
2548    /// It limits its result to a valid row.
2549    #[deprecated(since = "1.1.0", note = "replaced by relative_screen_to_pos()")]
2550    pub fn screen_to_row(&self, scy: i16) -> upos_type {
2551        let (_, oy) = self.offset();
2552        let oy = oy as upos_type + self.dark_offset.1 as upos_type;
2553
2554        if scy < 0 {
2555            oy.saturating_sub((scy as ipos_type).unsigned_abs())
2556        } else if scy as u16 >= (self.inner.height + self.dark_offset.1) {
2557            min(oy + scy as upos_type, self.len_lines().saturating_sub(1))
2558        } else {
2559            let scy = oy + scy as upos_type;
2560            let len = self.len_lines();
2561            if scy < len {
2562                scy
2563            } else {
2564                len.saturating_sub(1)
2565            }
2566        }
2567    }
2568
2569    /// Converts from a widget relative screen coordinate to a grapheme index.
2570    /// It limits its result to a valid column.
2571    ///
2572    /// * row is a row-index into the value, not a screen-row. It can be calculated
2573    ///   with screen_to_row().
2574    /// * x is the relative screen position.
2575    #[allow(deprecated)]
2576    #[deprecated(since = "1.1.0", note = "replaced by relative_screen_to_pos()")]
2577    pub fn screen_to_col(&self, row: upos_type, scx: i16) -> upos_type {
2578        self.try_screen_to_col(row, scx).expect("valid_row")
2579    }
2580
2581    /// Converts from a widget relative screen coordinate to a grapheme index.
2582    /// It limits its result to a valid column.
2583    ///
2584    /// * row is a row-index into the value, not a screen-row. It can be calculated
2585    ///   with screen_to_row().
2586    /// * x is the relative screen position.
2587    #[allow(deprecated)]
2588    #[deprecated(since = "1.1.0", note = "replaced by relative_screen_to_pos()")]
2589    pub fn try_screen_to_col(&self, row: upos_type, scx: i16) -> Result<upos_type, TextError> {
2590        let (ox, _) = self.offset();
2591
2592        let ox = ox as upos_type + self.dark_offset.0 as upos_type;
2593
2594        if scx < 0 {
2595            Ok(ox.saturating_sub((scx as ipos_type).unsigned_abs()))
2596        } else if scx as u16 >= (self.inner.width + self.dark_offset.0) {
2597            Ok(min(ox + scx as upos_type, self.line_width(row)))
2598        } else {
2599            let scx = scx as u16;
2600
2601            let line = self.try_glyphs(
2602                row..row + 1,
2603                ox as u16,
2604                self.inner.width + self.dark_offset.0,
2605            )?;
2606
2607            let mut col = ox;
2608            for g in line {
2609                if scx < g.screen_pos().0 + g.screen_width() {
2610                    break;
2611                }
2612                col = g.pos().x + 1;
2613            }
2614            Ok(col)
2615        }
2616    }
2617
2618    /// Converts the row of the position to a screen position
2619    /// relative to the widget area.
2620    #[deprecated(since = "1.1.0", note = "replaced by pos_to_relative_screen()")]
2621    pub fn row_to_screen(&self, pos: impl Into<TextPosition>) -> Option<u16> {
2622        let pos = pos.into();
2623        let (_, oy) = self.offset();
2624
2625        if pos.y < oy as upos_type {
2626            return None;
2627        }
2628
2629        let screen_y = pos.y - oy as upos_type;
2630
2631        if screen_y >= self.dark_offset.1 as upos_type {
2632            Some(screen_y as u16 - self.dark_offset.1)
2633        } else {
2634            None
2635        }
2636    }
2637
2638    /// Converts a grapheme based position to a screen position
2639    /// relative to the widget area.
2640    #[allow(deprecated)]
2641    #[deprecated(since = "1.1.0", note = "replaced by pos_to_relative_screen()")]
2642    pub fn col_to_screen(&self, pos: impl Into<TextPosition>) -> Option<u16> {
2643        self.try_col_to_screen(pos).expect("valid_pos")
2644    }
2645
2646    /// Converts a grapheme based position to a screen position
2647    /// relative to the widget area.
2648    #[allow(deprecated)]
2649    #[deprecated(since = "1.1.0", note = "replaced by pos_to_relative_screen()")]
2650    pub fn try_col_to_screen(
2651        &self,
2652        pos: impl Into<TextPosition>,
2653    ) -> Result<Option<u16>, TextError> {
2654        let pos = pos.into();
2655        let (ox, _) = self.offset();
2656
2657        if pos.x < ox as upos_type {
2658            return Ok(None);
2659        }
2660
2661        let line = self.try_glyphs(
2662            pos.y..pos.y + 1,
2663            ox as u16,
2664            self.inner.width + self.dark_offset.0,
2665        )?;
2666        let mut screen_x = 0;
2667        for g in line {
2668            if g.pos().x == pos.x {
2669                break;
2670            }
2671            screen_x = g.screen_pos().0 + g.screen_width();
2672        }
2673
2674        if screen_x >= self.dark_offset.0 {
2675            Ok(Some(screen_x - self.dark_offset.0))
2676        } else {
2677            Ok(None)
2678        }
2679    }
2680
2681    /// Set the cursor position from screen coordinates.
2682    ///
2683    /// The cursor positions are relative to the inner rect.
2684    /// They may be negative too, this allows setting the cursor
2685    /// to a position that is currently scrolled away.
2686    pub fn set_screen_cursor(&mut self, cursor: (i16, i16), extend_selection: bool) -> bool {
2687        let Some(cursor) = self.relative_screen_to_pos(cursor) else {
2688            return false;
2689        };
2690        if let Some(scr_cursor) = self.pos_to_relative_screen(cursor) {
2691            self.set_move_col(Some(scr_cursor.0));
2692        }
2693        self.set_cursor(cursor, extend_selection)
2694    }
2695
2696    /// Set the cursor position from screen coordinates,
2697    /// rounds the position to the next word start/end.
2698    ///
2699    /// The cursor positions are relative to the inner rect.
2700    /// They may be negative too, this allows setting the cursor
2701    /// to a position that is currently scrolled away.
2702    pub fn set_screen_cursor_words(&mut self, cursor: (i16, i16), extend_selection: bool) -> bool {
2703        let Some(cursor) = self.relative_screen_to_pos(cursor) else {
2704            return false;
2705        };
2706
2707        let anchor = self.anchor();
2708        let cursor = if cursor < anchor {
2709            self.word_start(cursor)
2710        } else {
2711            self.word_end(cursor)
2712        };
2713
2714        // extend anchor
2715        if !self.is_word_boundary(anchor) {
2716            if cursor < anchor {
2717                self.set_cursor(self.word_end(anchor), false);
2718            } else {
2719                self.set_cursor(self.word_start(anchor), false);
2720            }
2721        }
2722
2723        self.set_cursor(cursor, extend_selection)
2724    }
2725}
2726
2727impl TextAreaState {
2728    /// Maximum offset that is accessible with scrolling.
2729    ///
2730    /// This is shorter than the length of the content by whatever fills the last page.
2731    /// This is the base for the scrollbar content_length.
2732    pub fn vertical_max_offset(&self) -> usize {
2733        self.vscroll.max_offset()
2734    }
2735
2736    /// Current vertical offset.
2737    pub fn vertical_offset(&self) -> usize {
2738        self.vscroll.offset()
2739    }
2740
2741    /// Vertical page-size at the current offset.
2742    pub fn vertical_page(&self) -> usize {
2743        self.vscroll.page_len()
2744    }
2745
2746    /// Suggested scroll per scroll-event.
2747    pub fn vertical_scroll(&self) -> usize {
2748        self.vscroll.scroll_by()
2749    }
2750
2751    /// Maximum offset that is accessible with scrolling.
2752    ///
2753    /// This is currently set to usize::MAX.
2754    pub fn horizontal_max_offset(&self) -> usize {
2755        self.hscroll.max_offset()
2756    }
2757
2758    /// Current horizontal offset.
2759    pub fn horizontal_offset(&self) -> usize {
2760        self.hscroll.offset()
2761    }
2762
2763    /// Horizontal page-size at the current offset.
2764    pub fn horizontal_page(&self) -> usize {
2765        self.hscroll.page_len()
2766    }
2767
2768    /// Suggested scroll per scroll-event.
2769    pub fn horizontal_scroll(&self) -> usize {
2770        self.hscroll.scroll_by()
2771    }
2772
2773    /// Change the vertical offset.
2774    ///
2775    /// Due to overscroll it's possible that this is an invalid offset for the widget.
2776    /// The widget must deal with this situation.
2777    ///
2778    /// The widget returns true if the offset changed at all.
2779    pub fn set_vertical_offset(&mut self, row_offset: usize) -> bool {
2780        self.scroll_to_cursor = false;
2781        self.sub_row_offset = 0;
2782        self.vscroll.set_offset(row_offset)
2783    }
2784
2785    /// Change the horizontal offset.
2786    ///
2787    /// Due to overscroll it's possible that this is an invalid offset for the widget.
2788    /// The widget must deal with this situation.
2789    ///
2790    /// This offset is ignored if there is any text-wrapping.
2791    ///
2792    /// The widget returns true if the offset changed at all.
2793    pub fn set_horizontal_offset(&mut self, col_offset: usize) -> bool {
2794        self.scroll_to_cursor = false;
2795        self.hscroll.set_offset(col_offset)
2796    }
2797
2798    /// Scroll to position.
2799    pub fn scroll_to_row(&mut self, pos: usize) -> bool {
2800        self.scroll_to_cursor = false;
2801        self.vscroll.set_offset(pos)
2802    }
2803
2804    /// Scroll to position.
2805    ///
2806    /// This scroll-offset is ignored if there is any text-wrapping.
2807    pub fn scroll_to_col(&mut self, pos: usize) -> bool {
2808        self.scroll_to_cursor = false;
2809        self.hscroll.set_offset(pos)
2810    }
2811
2812    /// Scrolling
2813    pub fn scroll_up(&mut self, delta: usize) -> bool {
2814        if let Some(pos) = self.relative_screen_to_pos((0, -(delta as i16))) {
2815            self.sub_row_offset = pos.x;
2816            self.vscroll.set_offset(pos.y as usize);
2817            true
2818        } else {
2819            self.sub_row_offset = 0;
2820
2821            // this uses a different limit compared to vscroll.scroll_down()
2822            // otherwise it doesn't play well with the sub_row_offset.
2823            let old = self.vscroll.offset;
2824            self.vscroll.offset = min(
2825                self.vscroll.offset.saturating_sub(delta),
2826                self.vscroll
2827                    .max_offset
2828                    .saturating_add(self.vscroll.page_len())
2829                    .saturating_add(self.vscroll.overscroll_by()),
2830            );
2831            old != self.vscroll.offset
2832        }
2833    }
2834
2835    /// Scrolling
2836    pub fn scroll_down(&mut self, delta: usize) -> bool {
2837        if let Some(pos) = self.relative_screen_to_pos((0, delta as i16)) {
2838            self.sub_row_offset = pos.x;
2839            self.vscroll.set_offset(pos.y as usize);
2840            true
2841        } else {
2842            self.sub_row_offset = 0;
2843
2844            // this uses a different limit compared to vscroll.scroll_down()
2845            // otherwise it doesn't play well with the sub_row_offset.
2846            let old = self.vscroll.offset;
2847            self.vscroll.offset = min(
2848                self.vscroll.offset.saturating_add(delta),
2849                self.vscroll
2850                    .max_offset
2851                    .saturating_add(self.vscroll.page_len())
2852                    .saturating_add(self.vscroll.overscroll_by()),
2853            );
2854
2855            old != self.vscroll.offset
2856        }
2857    }
2858
2859    /// Scrolling
2860    ///
2861    /// Does nothing if there is any text-wrapping.
2862    pub fn scroll_left(&mut self, delta: usize) -> bool {
2863        self.hscroll.scroll_left(delta)
2864    }
2865
2866    /// Scrolling
2867    ///
2868    /// Does nothing if there is any text-wrapping.
2869    pub fn scroll_right(&mut self, delta: usize) -> bool {
2870        self.hscroll.scroll_right(delta)
2871    }
2872
2873    /// If there is some form of text-wrapping active, this
2874    /// changes the start-column for rendering the first visible
2875    /// line.
2876    ///
2877    /// The effect looks like scrolling the visible rows.
2878    pub fn scroll_sub_row_offset(&mut self, col: upos_type) -> bool {
2879        if let Ok(max_col) = self.try_line_width(self.offset().1 as upos_type) {
2880            self.sub_row_offset = min(col as upos_type, max_col);
2881        } else {
2882            self.sub_row_offset = 0;
2883        }
2884        true
2885    }
2886}
2887
2888impl TextAreaState {
2889    /// Scroll that the cursor is visible.
2890    /// All move-fn do this automatically.
2891    pub fn scroll_cursor_to_visible(&mut self) {
2892        self.scroll_to_cursor = true;
2893    }
2894}
2895
2896/// Adds focus-handling for a text-area.
2897///
2898/// Regular focus traversal with Tab is not possible for a TextArea,
2899/// but this adds Esc and SHIFT-Esc as special traversal keys.
2900///
2901/// This is a secondary event-handler and doesn't forward to Regular
2902/// event-handling.
2903impl<'a> HandleEvent<crossterm::event::Event, FocusTraversal<'a>, Outcome> for TextAreaState {
2904    fn handle(
2905        &mut self,
2906        event: &crossterm::event::Event,
2907        qualifier: FocusTraversal<'a>,
2908    ) -> Outcome {
2909        if self.is_focused() {
2910            match event {
2911                ct_event!(keycode press Esc) => {
2912                    qualifier.0.next_force();
2913                    Outcome::Changed
2914                }
2915                ct_event!(keycode press SHIFT-Esc) => {
2916                    qualifier.0.prev_force();
2917                    Outcome::Changed
2918                }
2919                _ => Outcome::Continue,
2920            }
2921        } else {
2922            Outcome::Continue
2923        }
2924    }
2925}
2926
2927impl HandleEvent<crossterm::event::Event, Regular, TextOutcome> for TextAreaState {
2928    fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> TextOutcome {
2929        // small helper ...
2930        fn tc(r: bool) -> TextOutcome {
2931            if r {
2932                TextOutcome::TextChanged
2933            } else {
2934                TextOutcome::Unchanged
2935            }
2936        }
2937
2938        let mut r = if self.is_focused() {
2939            match event {
2940                ct_event!(key press c)
2941                | ct_event!(key press SHIFT-c)
2942                | ct_event!(key press CONTROL_ALT-c) => tc(self.insert_char(*c)),
2943                ct_event!(keycode press Tab) => {
2944                    // ignore tab from focus
2945                    tc(if !self.focus.gained() {
2946                        self.insert_tab()
2947                    } else {
2948                        false
2949                    })
2950                }
2951                ct_event!(keycode press SHIFT-BackTab) => {
2952                    // ignore tab from focus
2953                    tc(if !self.focus.gained() {
2954                        self.insert_backtab()
2955                    } else {
2956                        false
2957                    })
2958                }
2959                ct_event!(keycode press Enter) => tc(self.insert_newline()),
2960                ct_event!(keycode press Backspace) => tc(self.delete_prev_char()),
2961                ct_event!(keycode press Delete) => tc(self.delete_next_char()),
2962                ct_event!(keycode press CONTROL-Backspace)
2963                | ct_event!(keycode press ALT-Backspace) => tc(self.delete_prev_word()),
2964                ct_event!(keycode press CONTROL-Delete) | ct_event!(keycode press ALT-Delete) => {
2965                    tc(self.delete_next_word())
2966                }
2967                ct_event!(key press CONTROL-'x') => tc(self.cut_to_clip()),
2968                ct_event!(key press CONTROL-'v') => tc(self.paste_from_clip()),
2969                ct_event!(key press CONTROL-'d') => tc(self.duplicate_text()),
2970                ct_event!(key press CONTROL-'y') => tc(self.delete_line()),
2971                ct_event!(key press CONTROL-'z') => tc(self.undo()),
2972                ct_event!(key press CONTROL_SHIFT-'Z') => tc(self.redo()),
2973
2974                ct_event!(key release _)
2975                | ct_event!(key release SHIFT-_)
2976                | ct_event!(key release CONTROL_ALT-_)
2977                | ct_event!(keycode release Tab)
2978                | ct_event!(keycode release Enter)
2979                | ct_event!(keycode release Backspace)
2980                | ct_event!(keycode release Delete)
2981                | ct_event!(keycode release CONTROL-Backspace)
2982                | ct_event!(keycode release ALT-Backspace)
2983                | ct_event!(keycode release CONTROL-Delete)
2984                | ct_event!(key release CONTROL-'x')
2985                | ct_event!(key release CONTROL-'v')
2986                | ct_event!(key release CONTROL-'d')
2987                | ct_event!(key release CONTROL-'y')
2988                | ct_event!(key release CONTROL-'z')
2989                | ct_event!(key release CONTROL_SHIFT-'Z') => TextOutcome::Unchanged,
2990                _ => TextOutcome::Continue,
2991            }
2992        } else {
2993            TextOutcome::Continue
2994        };
2995        if r == TextOutcome::Continue {
2996            r = self.handle(event, ReadOnly);
2997        }
2998        r
2999    }
3000}
3001
3002impl HandleEvent<crossterm::event::Event, ReadOnly, TextOutcome> for TextAreaState {
3003    fn handle(&mut self, event: &crossterm::event::Event, _keymap: ReadOnly) -> TextOutcome {
3004        let mut r = if self.is_focused() {
3005            match event {
3006                ct_event!(keycode press Left) => self.move_left(1, false).into(),
3007                ct_event!(keycode press Right) => self.move_right(1, false).into(),
3008                ct_event!(keycode press Up) => self.move_up(1, false).into(),
3009                ct_event!(keycode press Down) => self.move_down(1, false).into(),
3010                ct_event!(keycode press PageUp) => {
3011                    self.move_up(self.vertical_page() as u16, false).into()
3012                }
3013                ct_event!(keycode press PageDown) => {
3014                    self.move_down(self.vertical_page() as u16, false).into()
3015                }
3016                ct_event!(keycode press Home) => self.move_to_line_start(false).into(),
3017                ct_event!(keycode press End) => self.move_to_line_end(false).into(),
3018                ct_event!(keycode press CONTROL-Left) => self.move_to_prev_word(false).into(),
3019                ct_event!(keycode press CONTROL-Right) => self.move_to_next_word(false).into(),
3020                ct_event!(keycode press CONTROL-Up) => false.into(),
3021                ct_event!(keycode press CONTROL-Down) => false.into(),
3022                ct_event!(keycode press CONTROL-PageUp) => self.move_to_screen_start(false).into(),
3023                ct_event!(keycode press CONTROL-PageDown) => self.move_to_screen_end(false).into(),
3024                ct_event!(keycode press CONTROL-Home) => self.move_to_start(false).into(),
3025                ct_event!(keycode press CONTROL-End) => self.move_to_end(false).into(),
3026
3027                ct_event!(keycode press ALT-Left) => self.scroll_left(1).into(),
3028                ct_event!(keycode press ALT-Right) => self.scroll_right(1).into(),
3029                ct_event!(keycode press ALT-Up) => self.scroll_up(1).into(),
3030                ct_event!(keycode press ALT-Down) => self.scroll_down(1).into(),
3031                ct_event!(keycode press ALT-PageUp) => {
3032                    self.scroll_up(max(self.vertical_page() / 2, 1)).into()
3033                }
3034                ct_event!(keycode press ALT-PageDown) => {
3035                    self.scroll_down(max(self.vertical_page() / 2, 1)).into()
3036                }
3037                ct_event!(keycode press ALT_SHIFT-PageUp) => {
3038                    self.scroll_left(max(self.horizontal_page() / 5, 1)).into()
3039                }
3040                ct_event!(keycode press ALT_SHIFT-PageDown) => {
3041                    self.scroll_right(max(self.horizontal_page() / 5, 1)).into()
3042                }
3043
3044                ct_event!(keycode press SHIFT-Left) => self.move_left(1, true).into(),
3045                ct_event!(keycode press SHIFT-Right) => self.move_right(1, true).into(),
3046                ct_event!(keycode press SHIFT-Up) => self.move_up(1, true).into(),
3047                ct_event!(keycode press SHIFT-Down) => self.move_down(1, true).into(),
3048                ct_event!(keycode press SHIFT-PageUp) => {
3049                    self.move_up(self.vertical_page() as u16, true).into()
3050                }
3051                ct_event!(keycode press SHIFT-PageDown) => {
3052                    self.move_down(self.vertical_page() as u16, true).into()
3053                }
3054                ct_event!(keycode press SHIFT-Home) => self.move_to_line_start(true).into(),
3055                ct_event!(keycode press SHIFT-End) => self.move_to_line_end(true).into(),
3056                ct_event!(keycode press CONTROL_SHIFT-Left) => self.move_to_prev_word(true).into(),
3057                ct_event!(keycode press CONTROL_SHIFT-Right) => self.move_to_next_word(true).into(),
3058                ct_event!(keycode press CONTROL_SHIFT-Home) => self.move_to_start(true).into(),
3059                ct_event!(keycode press CONTROL_SHIFT-End) => self.move_to_end(true).into(),
3060                ct_event!(key press CONTROL-'a') => self.select_all().into(),
3061                ct_event!(key press CONTROL-'c') => self.copy_to_clip().into(),
3062
3063                ct_event!(keycode release Left)
3064                | ct_event!(keycode release Right)
3065                | ct_event!(keycode release Up)
3066                | ct_event!(keycode release Down)
3067                | ct_event!(keycode release PageUp)
3068                | ct_event!(keycode release PageDown)
3069                | ct_event!(keycode release Home)
3070                | ct_event!(keycode release End)
3071                | ct_event!(keycode release CONTROL-Left)
3072                | ct_event!(keycode release CONTROL-Right)
3073                | ct_event!(keycode release CONTROL-Up)
3074                | ct_event!(keycode release CONTROL-Down)
3075                | ct_event!(keycode release CONTROL-PageUp)
3076                | ct_event!(keycode release CONTROL-PageDown)
3077                | ct_event!(keycode release CONTROL-Home)
3078                | ct_event!(keycode release CONTROL-End)
3079                | ct_event!(keycode release ALT-Left)
3080                | ct_event!(keycode release ALT-Right)
3081                | ct_event!(keycode release ALT-Up)
3082                | ct_event!(keycode release ALT-Down)
3083                | ct_event!(keycode release ALT-PageUp)
3084                | ct_event!(keycode release ALT-PageDown)
3085                | ct_event!(keycode release ALT_SHIFT-PageUp)
3086                | ct_event!(keycode release ALT_SHIFT-PageDown)
3087                | ct_event!(keycode release SHIFT-Left)
3088                | ct_event!(keycode release SHIFT-Right)
3089                | ct_event!(keycode release SHIFT-Up)
3090                | ct_event!(keycode release SHIFT-Down)
3091                | ct_event!(keycode release SHIFT-PageUp)
3092                | ct_event!(keycode release SHIFT-PageDown)
3093                | ct_event!(keycode release SHIFT-Home)
3094                | ct_event!(keycode release SHIFT-End)
3095                | ct_event!(keycode release CONTROL_SHIFT-Left)
3096                | ct_event!(keycode release CONTROL_SHIFT-Right)
3097                | ct_event!(keycode release CONTROL_SHIFT-Home)
3098                | ct_event!(keycode release CONTROL_SHIFT-End)
3099                | ct_event!(key release CONTROL-'a')
3100                | ct_event!(key release CONTROL-'c') => TextOutcome::Unchanged,
3101                _ => TextOutcome::Continue,
3102            }
3103        } else {
3104            TextOutcome::Continue
3105        };
3106
3107        if r == TextOutcome::Continue {
3108            r = self.handle(event, MouseOnly);
3109        }
3110        r
3111    }
3112}
3113
3114impl HandleEvent<crossterm::event::Event, MouseOnly, TextOutcome> for TextAreaState {
3115    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> TextOutcome {
3116        flow!(match event {
3117            ct_event!(mouse any for m) if self.mouse.drag(self.inner, m) => {
3118                let cx = m.column as i16 - self.inner.x as i16;
3119                let cy = m.row as i16 - self.inner.y as i16;
3120                self.set_screen_cursor((cx, cy), true).into()
3121            }
3122            ct_event!(mouse any for m) if self.mouse.drag2(self.inner, m, KeyModifiers::ALT) => {
3123                let cx = m.column as i16 - self.inner.x as i16;
3124                let cy = m.row as i16 - self.inner.y as i16;
3125                self.set_screen_cursor_words((cx, cy), true).into()
3126            }
3127            ct_event!(mouse any for m) if self.mouse.doubleclick(self.inner, m) => {
3128                if let Some(test) = self.screen_to_pos((m.column, m.row)) {
3129                    let start = self.word_start(test);
3130                    let end = self.word_end(test);
3131                    self.set_selection(start, end).into()
3132                } else {
3133                    TextOutcome::Unchanged
3134                }
3135            }
3136            ct_event!(mouse down Left for column,row) => {
3137                if self.inner.contains((*column, *row).into()) {
3138                    let cx = (column - self.inner.x) as i16;
3139                    let cy = (row - self.inner.y) as i16;
3140                    self.set_screen_cursor((cx, cy), false).into()
3141                } else {
3142                    TextOutcome::Continue
3143                }
3144            }
3145            ct_event!(mouse down SHIFT-Left for column,row) => {
3146                if self.inner.contains((*column, *row).into()) {
3147                    let cx = (column - self.inner.x) as i16;
3148                    let cy = (row - self.inner.y) as i16;
3149                    self.set_screen_cursor((cx, cy), true).into()
3150                } else {
3151                    TextOutcome::Continue
3152                }
3153            }
3154            ct_event!(mouse down CONTROL-Left for column,row) => {
3155                if self.inner.contains((*column, *row).into()) {
3156                    let cx = (column - self.inner.x) as i16;
3157                    let cy = (row - self.inner.y) as i16;
3158                    self.set_screen_cursor((cx, cy), true).into()
3159                } else {
3160                    TextOutcome::Continue
3161                }
3162            }
3163            ct_event!(mouse down ALT-Left for column,row) => {
3164                if self.inner.contains((*column, *row).into()) {
3165                    let cx = (column - self.inner.x) as i16;
3166                    let cy = (row - self.inner.y) as i16;
3167                    self.set_screen_cursor_words((cx, cy), true).into()
3168                } else {
3169                    TextOutcome::Continue
3170                }
3171            }
3172            _ => TextOutcome::Continue,
3173        });
3174
3175        let mut sas = ScrollAreaState::new()
3176            .area(self.inner)
3177            .h_scroll(&mut self.hscroll)
3178            .v_scroll(&mut self.vscroll);
3179        let r = match sas.handle(event, MouseOnly) {
3180            ScrollOutcome::Up(v) => self.scroll_up(v),
3181            ScrollOutcome::Down(v) => self.scroll_down(v),
3182            ScrollOutcome::Left(v) => self.scroll_left(v),
3183            ScrollOutcome::Right(v) => self.scroll_right(v),
3184            ScrollOutcome::VPos(v) => self.set_vertical_offset(v),
3185            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v),
3186            _ => false,
3187        };
3188        if r {
3189            return TextOutcome::Changed;
3190        }
3191
3192        TextOutcome::Continue
3193    }
3194}
3195
3196/// Handle all events.
3197/// Text events are only processed if focus is true.
3198/// Mouse events are processed if they are in range.
3199pub fn handle_events(
3200    state: &mut TextAreaState,
3201    focus: bool,
3202    event: &crossterm::event::Event,
3203) -> TextOutcome {
3204    state.focus.set(focus);
3205    state.handle(event, Regular)
3206}
3207
3208/// Handle only navigation events.
3209/// Text events are only processed if focus is true.
3210/// Mouse events are processed if they are in range.
3211pub fn handle_readonly_events(
3212    state: &mut TextAreaState,
3213    focus: bool,
3214    event: &crossterm::event::Event,
3215) -> TextOutcome {
3216    state.focus.set(focus);
3217    state.handle(event, ReadOnly)
3218}
3219
3220/// Handle only mouse-events.
3221pub fn handle_mouse_events(
3222    state: &mut TextAreaState,
3223    event: &crossterm::event::Event,
3224) -> TextOutcome {
3225    state.handle(event, MouseOnly)
3226}