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