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