rat_text/text_store/
text_rope.rs

1use crate::grapheme::{RopeGraphemes, StrGraphemes};
2use crate::text_store::{Cursor, TextStore};
3use crate::{TextError, TextPosition, TextRange, upos_type};
4use ropey::{Rope, RopeSlice};
5use std::borrow::Cow;
6use std::cell::Cell;
7use std::cmp::min;
8use std::ops::Range;
9use unicode_segmentation::UnicodeSegmentation;
10
11/// Text store with a rope.
12#[derive(Debug, Clone, Default)]
13pub struct TextRope {
14    text: Rope,
15    // minimum byte position changed since last reset.
16    min_changed: Cell<Option<usize>>,
17    // tmp buf
18    buf: String,
19}
20
21impl TextRope {
22    /// New empty.
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// New from string.
28    pub fn new_text(t: &str) -> Self {
29        Self {
30            text: Rope::from_str(t),
31            min_changed: Default::default(),
32            buf: Default::default(),
33        }
34    }
35
36    /// New from rope.
37    pub fn new_rope(r: Rope) -> Self {
38        Self {
39            text: r,
40            min_changed: Default::default(),
41            buf: Default::default(),
42        }
43    }
44
45    /// Borrow the rope
46    pub fn rope(&self) -> &Rope {
47        &self.text
48    }
49
50    /// A range of the text as RopeSlice.
51    #[inline]
52    #[deprecated]
53    pub fn rope_slice(&self, range: TextRange) -> Result<RopeSlice<'_>, TextError> {
54        let s = self.byte_range(range)?;
55        Ok(self.text.get_byte_slice(s).expect("valid_range"))
56    }
57}
58
59impl TextRope {
60    fn invalidate(&self, byte_pos: usize) {
61        self.min_changed.update(|v| match v {
62            None => Some(byte_pos),
63            Some(w) => Some(min(byte_pos, w)),
64        });
65    }
66
67    #[inline]
68    #[allow(clippy::match_like_matches_macro)]
69    fn has_final_newline(&self) -> bool {
70        let len = self.text.len_bytes();
71        if len > 3 {
72            match (
73                self.text.get_byte(len - 3).expect("valid_pos"),
74                self.text.get_byte(len - 2).expect("valid_pos"),
75                self.text.get_byte(len - 1).expect("valid_pos"),
76            ) {
77                (_, _, b'\n')
78                | (_, _, b'\r')
79                | (_, _, 0x0c)
80                | (_, _, 0x0b)
81                | (_, _, 0x85)
82                | (0xE2, 0x80, 0xA8)
83                | (0xE2, 0x80, 0xA9) => true,
84                _ => false,
85            }
86        } else if len > 0 {
87            match self.text.get_byte(len - 1).expect("valid_pos") {
88                b'\n' | b'\r' | 0x0c | 0x0b | 0x85 => true,
89                _ => false,
90            }
91        } else {
92            false
93        }
94    }
95
96    fn normalize_row(&self, row: upos_type) -> Result<upos_type, TextError> {
97        let text_len = self.len_lines() as upos_type;
98        let rope_len = self.text.len_lines() as upos_type;
99
100        if row <= rope_len {
101            Ok(row)
102        } else if row <= text_len {
103            Ok(row - 1)
104        } else {
105            Err(TextError::LineIndexOutOfBounds(row, text_len))
106        }
107    }
108
109    fn normalize(&self, pos: TextPosition) -> Result<(TextPosition, usize), TextError> {
110        let len = self.len_lines();
111        if pos.y > len {
112            Err(TextError::LineIndexOutOfBounds(pos.y, len))
113        } else if pos.x > 0 && pos.y == len {
114            Err(TextError::ColumnIndexOutOfBounds(pos.x, 0))
115        } else if pos.x > 0 && pos.y == len - 1 && !self.has_final_newline() {
116            Err(TextError::ColumnIndexOutOfBounds(pos.x, 0))
117        } else if pos.x == 0 && pos.y == len {
118            let pos_byte = self.byte_range_at(pos)?;
119            Ok((
120                self.byte_to_pos(pos_byte.start).expect("valid-byte"),
121                pos_byte.start,
122            ))
123        } else if pos.x == 0 && pos.y == len - 1 && !self.has_final_newline() {
124            let pos_byte = self.byte_range_at(pos)?;
125            Ok((
126                self.byte_to_pos(pos_byte.start).expect("valid-byte"),
127                pos_byte.start,
128            ))
129        } else {
130            let pos_byte = self.byte_range_at(pos)?;
131            Ok((pos, pos_byte.start))
132        }
133    }
134}
135
136impl TextStore for TextRope {
137    type GraphemeIter<'a> = RopeGraphemes<'a>;
138
139    /// Can store multi-line content?
140    ///
141    /// If this returns false it is an error to call any function with
142    /// a row other than `0`.
143    fn is_multi_line(&self) -> bool {
144        true
145    }
146
147    /// Minimum byte position that has been changed
148    /// since the last call of min_changed().
149    ///
150    /// Used to invalidate caches.
151    fn cache_validity(&self) -> Option<usize> {
152        self.min_changed.take()
153    }
154
155    /// Content as string.
156    fn string(&self) -> String {
157        self.text.to_string()
158    }
159
160    /// Set content.
161    fn set_string(&mut self, t: &str) {
162        self.invalidate(0);
163        self.text = Rope::from_str(t);
164    }
165
166    /// Grapheme position to byte position.
167    /// This is the (start,end) position of the single grapheme after pos.
168    ///
169    /// * pos must be a valid position: row <= len_lines, col <= line_width of the row.
170    fn byte_range_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
171        let it_line = self.line_graphemes(pos.y)?;
172
173        let mut col = 0;
174        let mut byte_end = it_line.text_offset();
175        for grapheme in it_line {
176            if col == pos.x {
177                return Ok(grapheme.text_bytes());
178            }
179            col += 1;
180            byte_end = grapheme.text_bytes().end;
181        }
182        // one past the end is ok.
183        if col == pos.x {
184            Ok(byte_end..byte_end)
185        } else {
186            Err(TextError::ColumnIndexOutOfBounds(pos.x, col))
187        }
188    }
189
190    /// Grapheme range to byte range.
191    ///
192    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
193    fn byte_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
194        if range.start.y == range.end.y {
195            let it_line = self.line_graphemes(range.start.y)?;
196
197            let mut range_start = None;
198            let mut range_end = None;
199            let mut col = 0;
200            let mut byte_end = it_line.text_offset();
201            for grapheme in it_line {
202                if col == range.start.x {
203                    range_start = Some(grapheme.text_bytes().start);
204                }
205                if col == range.end.x {
206                    range_end = Some(grapheme.text_bytes().end);
207                }
208                if range_start.is_some() && range_end.is_some() {
209                    break;
210                }
211                col += 1;
212                byte_end = grapheme.text_bytes().end;
213            }
214            // one past the end is ok.
215            if col == range.start.x {
216                range_start = Some(byte_end);
217            }
218            if col == range.end.x {
219                range_end = Some(byte_end);
220            }
221
222            let Some(range_start) = range_start else {
223                return Err(TextError::ColumnIndexOutOfBounds(range.start.x, col));
224            };
225            let Some(range_end) = range_end else {
226                return Err(TextError::ColumnIndexOutOfBounds(range.end.x, col));
227            };
228
229            Ok(range_start..range_end)
230        } else {
231            let range_start = self.byte_range_at(range.start)?;
232            let range_end = self.byte_range_at(range.end)?;
233
234            Ok(range_start.start..range_end.start)
235        }
236    }
237
238    /// Byte position to grapheme position.
239    /// Returns the position that contains the given byte index.
240    ///
241    /// * byte must <= byte-len.
242    fn byte_to_pos(&self, byte_pos: usize) -> Result<TextPosition, TextError> {
243        let Ok(row) = self.text.try_byte_to_line(byte_pos) else {
244            return Err(TextError::ByteIndexOutOfBounds(
245                byte_pos,
246                self.text.len_bytes(),
247            ));
248        };
249        let row = row as upos_type;
250
251        let mut col = 0;
252        let it_line = self.line_graphemes(row)?;
253        for grapheme in it_line {
254            if byte_pos < grapheme.text_bytes().end {
255                break;
256            }
257            col += 1;
258        }
259
260        Ok(TextPosition::new(col, row))
261    }
262
263    /// Byte range to grapheme range.
264    ///
265    /// * byte must <= byte-len.
266    fn bytes_to_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
267        let Ok(start_row) = self.text.try_byte_to_line(bytes.start) else {
268            return Err(TextError::ByteIndexOutOfBounds(
269                bytes.start,
270                self.text.len_bytes(),
271            ));
272        };
273        let start_row = start_row as upos_type;
274        let Ok(end_row) = self.text.try_byte_to_line(bytes.end) else {
275            return Err(TextError::ByteIndexOutOfBounds(
276                bytes.end,
277                self.text.len_bytes(),
278            ));
279        };
280        let end_row = end_row as upos_type;
281
282        if start_row == end_row {
283            let mut col = 0;
284            let mut start = None;
285            let mut end = None;
286            let it_line = self.line_graphemes(start_row)?;
287            for grapheme in it_line {
288                if bytes.start < grapheme.text_bytes().end {
289                    if start.is_none() {
290                        start = Some(col);
291                    }
292                }
293                if bytes.end < grapheme.text_bytes().end {
294                    if end.is_none() {
295                        end = Some(col);
296                    }
297                }
298                if start.is_some() && end.is_some() {
299                    break;
300                }
301                col += 1;
302            }
303            if bytes.start == self.text.len_bytes() {
304                start = Some(col);
305            }
306            if bytes.end == self.text.len_bytes() {
307                end = Some(col);
308            }
309
310            let Some(start) = start else {
311                return Err(TextError::ByteIndexOutOfBounds(
312                    bytes.start,
313                    self.text.len_bytes(),
314                ));
315            };
316            let Some(end) = end else {
317                return Err(TextError::ByteIndexOutOfBounds(
318                    bytes.end,
319                    self.text.len_bytes(),
320                ));
321            };
322
323            Ok(TextRange::new((start, start_row), (end, end_row)))
324        } else {
325            let start = self.byte_to_pos(bytes.start)?;
326            let end = self.byte_to_pos(bytes.end)?;
327
328            Ok(TextRange::new(start, end))
329        }
330    }
331
332    /// A range of the text as `Cow<str>`.
333    ///
334    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
335    /// * pos must be inside of range.
336    fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
337        let range = self.byte_range(range)?;
338        let v = self.text.byte_slice(range);
339        match v.as_str() {
340            Some(v) => Ok(Cow::Borrowed(v)),
341            None => Ok(Cow::Owned(v.to_string())),
342        }
343    }
344
345    /// A range of the text as `Cow<str>`.
346    ///
347    /// The byte-range must be a valid range.
348    fn str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
349        let Some(v) = self.text.get_byte_slice(range.clone()) else {
350            return Err(TextError::ByteRangeOutOfBounds(
351                Some(range.start),
352                Some(range.end),
353                self.text.len_bytes(),
354            ));
355        };
356        match v.as_str() {
357            Some(v) => Ok(Cow::Borrowed(v)),
358            None => Ok(Cow::Owned(v.to_string())),
359        }
360    }
361
362    /// Return a cursor over the graphemes of the range, start at the given position.
363    ///
364    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
365    /// * pos must be inside of range.
366    fn graphemes(
367        &self,
368        range: TextRange,
369        pos: TextPosition,
370    ) -> Result<Self::GraphemeIter<'_>, TextError> {
371        if !range.contains_pos(pos) && range.end != pos {
372            return Err(TextError::TextPositionOutOfBounds(pos));
373        }
374
375        let range_bytes = self.byte_range(range)?;
376        let pos_byte = self.byte_range_at(pos)?.start;
377
378        let s = self
379            .text
380            .get_byte_slice(range_bytes.clone())
381            .expect("valid_range");
382
383        let r = RopeGraphemes::new_offset(range_bytes.start, s, pos_byte - range_bytes.start)
384            .expect("valid_bytes");
385
386        Ok(r)
387    }
388
389    /// Return a cursor over the graphemes of the range, start at the given position.
390    ///
391    /// * range must be a valid byte-range.
392    /// * pos must be inside of range.
393    fn graphemes_byte(
394        &self,
395        range: Range<usize>,
396        pos: usize,
397    ) -> Result<Self::GraphemeIter<'_>, TextError> {
398        if !range.contains(&pos) && range.end != pos {
399            return Err(TextError::ByteIndexOutOfBounds(pos, range.end));
400        }
401
402        let Some(s) = self.text.get_byte_slice(range.clone()) else {
403            return Err(TextError::ByteRangeInvalid(range.start, range.end));
404        };
405
406        let r = RopeGraphemes::new_offset(range.start, s, pos - range.start)?;
407
408        Ok(r)
409    }
410
411    /// Line as str.
412    ///
413    /// * row must be <= len_lines
414    fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
415        let len = self.len_lines() as upos_type;
416        if row < len {
417            if row < self.text.len_lines() as upos_type {
418                let v = self.text.get_line(row as usize).expect("valid_row");
419                match v.as_str() {
420                    Some(v) => Ok(Cow::Borrowed(v)),
421                    None => Ok(Cow::Owned(v.to_string())),
422                }
423            } else {
424                Ok(Cow::Borrowed(""))
425            }
426        } else {
427            Err(TextError::LineIndexOutOfBounds(row, len))
428        }
429    }
430
431    /// Iterate over text-lines, starting at line-offset.
432    ///
433    /// * row must be <= len_lines
434    fn lines_at(&self, row: upos_type) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
435        let len = self.len_lines() as upos_type;
436        if row < len {
437            let it = self.text.get_lines_at(row as usize).expect("valid_row");
438            Ok(it.map(|v| match v.as_str() {
439                Some(v) => Cow::Borrowed(v),
440                None => Cow::Owned(v.to_string()),
441            }))
442        } else {
443            Err(TextError::LineIndexOutOfBounds(row, len))
444        }
445    }
446
447    /// Return a line as an iterator over the graphemes.
448    /// This contains the '\n' at the end.
449    ///
450    /// * row must be <= len_lines
451    #[inline]
452    fn line_graphemes(&self, row: upos_type) -> Result<Self::GraphemeIter<'_>, TextError> {
453        let row = self.normalize_row(row)?;
454        let line_byte = self.text.try_line_to_byte(row as usize)?;
455        let line = if row < self.text.len_lines() as upos_type {
456            self.text.get_line(row as usize).expect("valid_row")
457        } else {
458            RopeSlice::from("")
459        };
460        Ok(RopeGraphemes::new(line_byte, line))
461    }
462
463    /// Line width as grapheme count.
464    /// Excludes the terminating '\n'.
465    ///
466    /// * row must be <= len_lines
467    #[inline]
468    fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
469        let row = self.normalize_row(row)?;
470
471        if row < self.text.len_lines() as upos_type {
472            let r = self.text.get_line(row as usize).expect("valid_row");
473            let len = RopeGraphemes::new(0, r)
474                .filter(|g| !g.is_line_break())
475                .count() as upos_type;
476            Ok(len)
477        } else {
478            Ok(0)
479        }
480    }
481
482    #[inline]
483    #[allow(clippy::needless_bool)]
484    fn should_insert_newline(&self, pos: TextPosition) -> bool {
485        if pos.x == 0 && pos.y == 0 {
486            false
487        } else if pos.x == 0 && pos.y == self.len_lines() && !self.has_final_newline() {
488            true
489        } else if pos.x == 0 && pos.y == self.len_lines() - 1 && !self.has_final_newline() {
490            true
491        } else {
492            false
493        }
494    }
495
496    #[inline]
497    fn len_lines(&self) -> upos_type {
498        match self.text.len_bytes() {
499            0 => 1,
500            _ => {
501                let l = self.text.len_lines();
502                let t = if self.has_final_newline() { 0 } else { 1 };
503                (l + t) as upos_type
504            }
505        }
506    }
507
508    /// Insert a char at the given position.
509    ///
510    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
511    fn insert_char(
512        &mut self,
513        mut pos: TextPosition,
514        ch: char,
515    ) -> Result<(TextRange, Range<usize>), TextError> {
516        // normalize the position (0, len_lines) to something sane.
517        let pos_byte;
518        (pos, pos_byte) = self.normalize(pos)?;
519
520        // invalidate cache
521        self.invalidate(pos_byte);
522
523        let mut it_gr =
524            RopeGraphemes::new_offset(0, self.text.slice(..), pos_byte).expect("valid_bytes");
525        let prev = it_gr.prev();
526        it_gr.next();
527        let next = it_gr.next();
528
529        let insert_range = if ch == '\n' {
530            if let Some(prev) = prev {
531                if prev == "\r" {
532                    TextRange::new(pos, pos)
533                } else {
534                    TextRange::new(pos, (0, pos.y + 1))
535                }
536            } else {
537                TextRange::new(pos, (0, pos.y + 1))
538            }
539        } else if ch == '\r' {
540            if let Some(next) = next {
541                if next == "\n" {
542                    TextRange::new(pos, pos)
543                } else {
544                    TextRange::new(pos, (0, pos.y + 1))
545                }
546            } else {
547                TextRange::new(pos, (0, pos.y + 1))
548            }
549        } else if cfg!(feature = "unicode_lines")
550            && (ch == '\u{000C}'
551                || ch == '\u{000B}'
552                || ch == '\u{0085}'
553                || ch == '\u{2028}'
554                || ch == '\u{2029}')
555        {
556            TextRange::new(pos, (0, pos.y + 1))
557        } else {
558            // test for combining codepoints.
559            let mut len = 0;
560            self.buf.clear();
561            if let Some(prev) = prev {
562                len += 1;
563                self.buf.push_str(prev.grapheme());
564            }
565            len += 1;
566            self.buf.push(ch);
567            if let Some(next) = next {
568                len += 1;
569                self.buf.push_str(next.grapheme());
570            }
571            let buf_len = self.buf.graphemes(true).count();
572
573            let n = len - buf_len;
574
575            if n == 0 {
576                TextRange::new(pos, (pos.x + 1, pos.y))
577            } else if n == 1 {
578                // combined some
579                TextRange::new(pos, pos)
580            } else if n == 2 {
581                // combined some
582                TextRange::new(pos, pos)
583            } else {
584                unreachable!("insert_char {:?}", self.buf);
585            }
586        };
587
588        let pos_char = self.text.try_byte_to_char(pos_byte).expect("valid_bytes");
589
590        self.text
591            .try_insert_char(pos_char, ch)
592            .expect("valid_chars");
593
594        Ok((insert_range, pos_byte..pos_byte + ch.len_utf8()))
595    }
596
597    /// Insert a text str at the given position.
598    ///
599    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
600    fn insert_str(
601        &mut self,
602        mut pos: TextPosition,
603        txt: &str,
604    ) -> Result<(TextRange, Range<usize>), TextError> {
605        // normalize the position (0, len_lines-1) to something sane.
606        let pos_byte;
607        (pos, pos_byte) = self.normalize(pos)?;
608
609        self.invalidate(pos_byte);
610
611        let pos_char = self.text.try_byte_to_char(pos_byte).expect("valid_bytes");
612
613        let mut line_count = 0;
614        let mut last_linebreak_idx = 0;
615        for c in StrGraphemes::new(0, txt) {
616            let test = if cfg!(feature = "cr_lines") {
617                c == "\r" || c == "\n" || c == "\r\n"
618            } else if cfg!(feature = "unicode_lines") {
619                c == "\r"
620                    || c == "\n"
621                    || c == "\r\n"
622                    || c == "\u{000C}"
623                    || c == "\u{000B}"
624                    || c == "\u{0085}"
625                    || c == "\u{2028}"
626                    || c == "\u{2029}"
627            } else {
628                c == "\n" || c == "\r\n"
629            };
630
631            if test {
632                line_count += 1;
633                last_linebreak_idx = c.text_bytes().end;
634            }
635        }
636
637        let insert_range = if line_count > 0 {
638            // the remainder of the line after pos extends the last line of
639            // the inserted text. they might combine in some way.
640
641            // Fill in the last line of the inserted text.
642            self.buf.clear();
643            self.buf.push_str(&txt[last_linebreak_idx..]);
644            let old_offset = self.buf.len();
645
646            // Fill in the remainder of the current text after the insert position.
647            let line_offset = self
648                .text
649                .try_line_to_byte(pos.y as usize)
650                .expect("valid-pos");
651            let split = self //
652                .byte_range_at(pos)
653                .expect("valid_pos")
654                .start
655                - line_offset;
656            let remainder = self
657                .text
658                .get_line(pos.y as usize)
659                .expect("valid-pos")
660                .get_byte_slice(split..)
661                .expect("valid-pos");
662            for cc in remainder.chars() {
663                self.buf.push(cc);
664            }
665            let new_len = self.buf.graphemes(true).count() as upos_type;
666            let old_len = self.buf[old_offset..].graphemes(true).count() as upos_type;
667
668            self.text.try_insert(pos_char, txt).expect("valid_pos");
669
670            TextRange::new(pos, (new_len - old_len, pos.y + line_count))
671        } else {
672            // no way to know if the insert text combines with a surrounding char.
673            // the difference of the grapheme len seems safe though.
674            let old_len = self.line_width(pos.y).expect("valid_line");
675            self.text.try_insert(pos_char, txt).expect("valid_pos");
676            let new_len = self.line_width(pos.y).expect("valid_line");
677
678            TextRange::new(pos, (pos.x + new_len - old_len, pos.y))
679        };
680
681        Ok((insert_range, pos_byte..pos_byte + txt.len()))
682    }
683
684    /// Remove the given text range.
685    ///
686    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
687    fn remove(
688        &mut self,
689        mut range: TextRange,
690    ) -> Result<(String, (TextRange, Range<usize>)), TextError> {
691        let start_byte_pos;
692        let end_byte_pos;
693
694        (range.start, start_byte_pos) = self.normalize(range.start)?;
695        (range.end, end_byte_pos) = self.normalize(range.end)?;
696
697        self.invalidate(start_byte_pos);
698
699        let old_text = self
700            .text
701            .get_byte_slice(start_byte_pos..end_byte_pos)
702            .expect("valid_bytes");
703        let old_text = old_text.to_string();
704
705        let start_pos = self
706            .text
707            .try_byte_to_char(start_byte_pos)
708            .expect("valid_bytes");
709        let end_pos = self
710            .text
711            .try_byte_to_char(end_byte_pos)
712            .expect("valid_bytes");
713
714        self.text.try_remove(start_pos..end_pos).expect("valid_pos");
715
716        Ok((old_text, (range, start_byte_pos..end_byte_pos)))
717    }
718
719    /// Insert a string at the given byte index.
720    /// Call this only for undo.
721    ///
722    /// byte_pos must be <= len bytes.
723    fn insert_b(&mut self, byte_pos: usize, t: &str) -> Result<(), TextError> {
724        let pos_char = self.text.try_byte_to_char(byte_pos)?;
725
726        self.invalidate(byte_pos);
727        self.text.try_insert(pos_char, t).expect("valid_pos");
728        Ok(())
729    }
730
731    /// Remove the given byte-range.
732    /// Call this only for undo.
733    ///
734    /// byte_pos must be <= len bytes.
735    fn remove_b(&mut self, byte_range: Range<usize>) -> Result<(), TextError> {
736        let start_char = self.text.try_byte_to_char(byte_range.start)?;
737        let end_char = self.text.try_byte_to_char(byte_range.end)?;
738
739        self.invalidate(byte_range.start);
740        self.text
741            .try_remove(start_char..end_char)
742            .expect("valid_range");
743        Ok(())
744    }
745}
746
747impl From<ropey::Error> for TextError {
748    fn from(err: ropey::Error) -> Self {
749        use ropey::Error;
750        match err {
751            Error::ByteIndexOutOfBounds(i, l) => TextError::ByteIndexOutOfBounds(i, l),
752            Error::CharIndexOutOfBounds(i, l) => TextError::CharIndexOutOfBounds(i, l),
753            Error::LineIndexOutOfBounds(i, l) => {
754                TextError::LineIndexOutOfBounds(i as upos_type, l as upos_type)
755            }
756            Error::Utf16IndexOutOfBounds(_, _) => {
757                unreachable!("{:?}", err)
758            }
759            Error::ByteIndexNotCharBoundary(i) => TextError::ByteIndexNotCharBoundary(i),
760            Error::ByteRangeNotCharBoundary(s, e) => TextError::ByteRangeNotCharBoundary(s, e),
761            Error::ByteRangeInvalid(s, e) => TextError::ByteRangeInvalid(s, e),
762            Error::CharRangeInvalid(s, e) => TextError::CharRangeInvalid(s, e),
763            Error::ByteRangeOutOfBounds(s, e, l) => TextError::ByteRangeOutOfBounds(s, e, l),
764            Error::CharRangeOutOfBounds(s, e, l) => TextError::CharRangeOutOfBounds(s, e, l),
765            _ => {
766                unreachable!("{:?}", err)
767            }
768        }
769    }
770}