Skip to main content

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