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 ch == '\u{000C}'
550            || ch == '\u{000B}'
551            || ch == '\u{0085}'
552            || ch == '\u{2028}'
553            || ch == '\u{2029}'
554        {
555            TextRange::new(pos, (0, pos.y + 1))
556        } else {
557            // test for combining codepoints.
558            let mut len = 0;
559            self.buf.clear();
560            if let Some(prev) = prev {
561                len += 1;
562                self.buf.push_str(prev.grapheme());
563            }
564            len += 1;
565            self.buf.push(ch);
566            if let Some(next) = next {
567                len += 1;
568                self.buf.push_str(next.grapheme());
569            }
570            let buf_len = self.buf.graphemes(true).count();
571
572            let n = len - buf_len;
573
574            if n == 0 {
575                TextRange::new(pos, (pos.x + 1, pos.y))
576            } else if n == 1 {
577                // combined some
578                TextRange::new(pos, pos)
579            } else if n == 2 {
580                // combined some
581                TextRange::new(pos, pos)
582            } else {
583                unreachable!("insert_char {:?}", self.buf);
584            }
585        };
586
587        let pos_char = self.text.try_byte_to_char(pos_byte).expect("valid_bytes");
588
589        self.text
590            .try_insert_char(pos_char, ch)
591            .expect("valid_chars");
592
593        Ok((insert_range, pos_byte..pos_byte + ch.len_utf8()))
594    }
595
596    /// Insert a text str at the given position.
597    ///
598    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
599    fn insert_str(
600        &mut self,
601        mut pos: TextPosition,
602        txt: &str,
603    ) -> Result<(TextRange, Range<usize>), TextError> {
604        // normalize the position (0, len_lines-1) to something sane.
605        let pos_byte;
606        (pos, pos_byte) = self.normalize(pos)?;
607
608        self.invalidate(pos_byte);
609
610        let pos_char = self.text.try_byte_to_char(pos_byte).expect("valid_bytes");
611
612        let mut line_count = 0;
613        let mut last_linebreak_idx = 0;
614        for c in StrGraphemes::new(0, txt) {
615            if c == "\r"
616                || c == "\n"
617                || c == "\r\n"
618                || c == "\u{000C}"
619                || c == "\u{000B}"
620                || c == "\u{0085}"
621                || c == "\u{2028}"
622                || c == "\u{2029}"
623            {
624                line_count += 1;
625                last_linebreak_idx = c.text_bytes().end;
626            }
627        }
628
629        let insert_range = if line_count > 0 {
630            // the remainder of the line after pos extends the last line of
631            // the inserted text. they might combine in some way.
632
633            // Fill in the last line of the inserted text.
634            self.buf.clear();
635            self.buf.push_str(&txt[last_linebreak_idx..]);
636            let old_offset = self.buf.len();
637
638            // Fill in the remainder of the current text after the insert position.
639            let line_offset = self
640                .text
641                .try_line_to_byte(pos.y as usize)
642                .expect("valid-pos");
643            let split = self //
644                .byte_range_at(pos)
645                .expect("valid_pos")
646                .start
647                - line_offset;
648            let remainder = self
649                .text
650                .get_line(pos.y as usize)
651                .expect("valid-pos")
652                .get_byte_slice(split..)
653                .expect("valid-pos");
654            for cc in remainder.chars() {
655                self.buf.push(cc);
656            }
657            let new_len = self.buf.graphemes(true).count() as upos_type;
658            let old_len = self.buf[old_offset..].graphemes(true).count() as upos_type;
659
660            self.text.try_insert(pos_char, txt).expect("valid_pos");
661
662            TextRange::new(pos, (new_len - old_len, pos.y + line_count))
663        } else {
664            // no way to know if the insert text combines with a surrounding char.
665            // the difference of the grapheme len seems safe though.
666            let old_len = self.line_width(pos.y).expect("valid_line");
667            self.text.try_insert(pos_char, txt).expect("valid_pos");
668            let new_len = self.line_width(pos.y).expect("valid_line");
669
670            TextRange::new(pos, (pos.x + new_len - old_len, pos.y))
671        };
672
673        Ok((insert_range, pos_byte..pos_byte + txt.len()))
674    }
675
676    /// Remove the given text range.
677    ///
678    /// * range must be a valid range. row <= len_lines, col <= line_width of the row.
679    fn remove(
680        &mut self,
681        mut range: TextRange,
682    ) -> Result<(String, (TextRange, Range<usize>)), TextError> {
683        let start_byte_pos;
684        let end_byte_pos;
685
686        (range.start, start_byte_pos) = self.normalize(range.start)?;
687        (range.end, end_byte_pos) = self.normalize(range.end)?;
688
689        self.invalidate(start_byte_pos);
690
691        let old_text = self
692            .text
693            .get_byte_slice(start_byte_pos..end_byte_pos)
694            .expect("valid_bytes");
695        let old_text = old_text.to_string();
696
697        let start_pos = self
698            .text
699            .try_byte_to_char(start_byte_pos)
700            .expect("valid_bytes");
701        let end_pos = self
702            .text
703            .try_byte_to_char(end_byte_pos)
704            .expect("valid_bytes");
705
706        self.text.try_remove(start_pos..end_pos).expect("valid_pos");
707
708        Ok((old_text, (range, start_byte_pos..end_byte_pos)))
709    }
710
711    /// Insert a string at the given byte index.
712    /// Call this only for undo.
713    ///
714    /// byte_pos must be <= len bytes.
715    fn insert_b(&mut self, byte_pos: usize, t: &str) -> Result<(), TextError> {
716        let pos_char = self.text.try_byte_to_char(byte_pos)?;
717
718        self.invalidate(byte_pos);
719        self.text.try_insert(pos_char, t).expect("valid_pos");
720        Ok(())
721    }
722
723    /// Remove the given byte-range.
724    /// Call this only for undo.
725    ///
726    /// byte_pos must be <= len bytes.
727    fn remove_b(&mut self, byte_range: Range<usize>) -> Result<(), TextError> {
728        let start_char = self.text.try_byte_to_char(byte_range.start)?;
729        let end_char = self.text.try_byte_to_char(byte_range.end)?;
730
731        self.invalidate(byte_range.start);
732        self.text
733            .try_remove(start_char..end_char)
734            .expect("valid_range");
735        Ok(())
736    }
737}
738
739impl From<ropey::Error> for TextError {
740    fn from(err: ropey::Error) -> Self {
741        use ropey::Error;
742        match err {
743            Error::ByteIndexOutOfBounds(i, l) => TextError::ByteIndexOutOfBounds(i, l),
744            Error::CharIndexOutOfBounds(i, l) => TextError::CharIndexOutOfBounds(i, l),
745            Error::LineIndexOutOfBounds(i, l) => {
746                TextError::LineIndexOutOfBounds(i as upos_type, l as upos_type)
747            }
748            Error::Utf16IndexOutOfBounds(_, _) => {
749                unreachable!("{:?}", err)
750            }
751            Error::ByteIndexNotCharBoundary(i) => TextError::ByteIndexNotCharBoundary(i),
752            Error::ByteRangeNotCharBoundary(s, e) => TextError::ByteRangeNotCharBoundary(s, e),
753            Error::ByteRangeInvalid(s, e) => TextError::ByteRangeInvalid(s, e),
754            Error::CharRangeInvalid(s, e) => TextError::CharRangeInvalid(s, e),
755            Error::ByteRangeOutOfBounds(s, e, l) => TextError::ByteRangeOutOfBounds(s, e, l),
756            Error::CharRangeOutOfBounds(s, e, l) => TextError::CharRangeOutOfBounds(s, e, l),
757            _ => {
758                unreachable!("{:?}", err)
759            }
760        }
761    }
762}