rat_text/
text_area.rs

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