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