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