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