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