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