floem_editor_core/buffer/
rope_text.rs

1use std::{borrow::Cow, ops::Range};
2
3use lapce_xi_rope::{interval::IntervalBounds, rope::ChunkIter, Cursor, Rope};
4
5use crate::{mode::Mode, paragraph::ParagraphCursor, word::WordCursor};
6
7pub trait RopeText {
8    fn text(&self) -> &Rope;
9
10    fn len(&self) -> usize {
11        self.text().len()
12    }
13
14    fn is_empty(&self) -> bool {
15        self.len() == 0
16    }
17
18    /// The last line of the held rope
19    fn last_line(&self) -> usize {
20        self.line_of_offset(self.len())
21    }
22
23    /// Get the offset into the rope of the start of the given line.
24    /// If the line it out of bounds, then the last offset (the len) is returned.
25    fn offset_of_line(&self, line: usize) -> usize {
26        let last_line = self.last_line();
27        let line = line.min(last_line + 1);
28        self.text().offset_of_line(line)
29    }
30
31    fn offset_line_end(&self, offset: usize, caret: bool) -> usize {
32        let line = self.line_of_offset(offset);
33        self.line_end_offset(line, caret)
34    }
35
36    fn line_of_offset(&self, offset: usize) -> usize {
37        let offset = offset.min(self.len());
38        let offset = self
39            .text()
40            .at_or_prev_codepoint_boundary(offset)
41            .unwrap_or(offset);
42
43        self.text().line_of_offset(offset)
44    }
45
46    fn offset_to_line_col(&self, offset: usize) -> (usize, usize) {
47        let offset = offset.min(self.len());
48        let line = self.line_of_offset(offset);
49        let line_start = self.offset_of_line(line);
50        if offset == line_start {
51            return (line, 0);
52        }
53
54        let col = offset - line_start;
55        (line, col)
56    }
57
58    /// Get the offset for a specific line and column.  
59    /// This should be preferred over simply adding the column to the line offset, because it
60    /// validates better and avoids returning newlines.
61    /// ```rust
62    /// # use floem_editor_core::xi_rope::Rope;
63    /// # use floem_editor_core::buffer::rope_text::{RopeText, RopeTextRef};
64    /// let text = Rope::from("hello\nworld");
65    /// let text = RopeTextRef::new(&text);
66    /// assert_eq!(text.offset_of_line_col(0, 0), 0);  // "h"
67    /// assert_eq!(text.offset_of_line_col(0, 4), 4);  // "o"
68    /// assert_eq!(text.offset_of_line_col(0, 5), 5);  // "\n"
69    /// assert_eq!(text.offset_of_line_col(0, 6), 5);  // "\n", avoids newline
70    /// assert_eq!(text.offset_of_line_col(1, 0), 6);  // "w"
71    /// assert_eq!(text.offset_of_line_col(1, 4), 10); // "d"
72    /// let text = Rope::from("hello\r\nworld");
73    /// let text = RopeTextRef::new(&text);
74    /// assert_eq!(text.offset_of_line_col(0, 0), 0);  // "h"
75    /// assert_eq!(text.offset_of_line_col(0, 4), 4);  // "o"
76    /// assert_eq!(text.offset_of_line_col(0, 5), 5);  // "\r"
77    /// assert_eq!(text.offset_of_line_col(0, 6), 5);  // "\r", avoids being in the middle
78    /// assert_eq!(text.offset_of_line_col(1, 0), 7);  // "w"
79    /// assert_eq!(text.offset_of_line_col(1, 4), 11); // "d"
80    /// ````
81    fn offset_of_line_col(&self, line: usize, col: usize) -> usize {
82        let mut pos = 0;
83        let mut offset = self.offset_of_line(line);
84        let text = self.slice_to_cow(offset..self.offset_of_line(line + 1));
85        let mut iter = text.chars().peekable();
86        while let Some(c) = iter.next() {
87            // Stop at the end of the line
88            if c == '\n' || (c == '\r' && iter.peek() == Some(&'\n')) {
89                return offset;
90            }
91
92            let char_len = c.len_utf8();
93            if pos + char_len > col {
94                return offset;
95            }
96            pos += char_len;
97            offset += char_len;
98        }
99        offset
100    }
101
102    fn line_end_col(&self, line: usize, caret: bool) -> usize {
103        let line_start = self.offset_of_line(line);
104        let offset = self.line_end_offset(line, caret);
105        offset - line_start
106    }
107
108    /// Get the offset of the end of the line. The caret decides whether it is after the last
109    /// character, or before it.
110    /// If the line is out of bounds, then the last offset (the len) is returned.  
111    /// ```rust
112    /// # use floem_editor_core::xi_rope::Rope;
113    /// # use floem_editor_core::buffer::rope_text::{RopeText, RopeTextRef};
114    /// let text = Rope::from("hello\nworld");
115    /// let text = RopeTextRef::new(&text);
116    /// assert_eq!(text.line_end_offset(0, false), 4);  // "hell|o"
117    /// assert_eq!(text.line_end_offset(0, true), 5);   // "hello|"
118    /// assert_eq!(text.line_end_offset(1, false), 10); // "worl|d"
119    /// assert_eq!(text.line_end_offset(1, true), 11);  // "world|"
120    /// // Out of bounds
121    /// assert_eq!(text.line_end_offset(2, false), 11); // "world|"
122    /// ```
123    fn line_end_offset(&self, line: usize, caret: bool) -> usize {
124        let mut offset = self.offset_of_line(line + 1);
125        let mut line_content: &str = &self.line_content(line);
126        if line_content.ends_with("\r\n") {
127            offset -= 2;
128            line_content = &line_content[..line_content.len() - 2];
129        } else if line_content.ends_with('\n') {
130            offset -= 1;
131            line_content = &line_content[..line_content.len() - 1];
132        }
133        if !caret && !line_content.is_empty() {
134            offset = self.prev_grapheme_offset(offset, 1, 0);
135        }
136        offset
137    }
138
139    /// Returns the content of the given line.
140    /// Includes the line ending if it exists. (-> the last line won't have a line ending)
141    /// Lines past the end of the document will return an empty string.
142    fn line_content(&self, line: usize) -> Cow<'_, str> {
143        self.text()
144            .slice_to_cow(self.offset_of_line(line)..self.offset_of_line(line + 1))
145    }
146
147    /// Get the offset of the previous grapheme cluster.
148    fn prev_grapheme_offset(&self, offset: usize, count: usize, limit: usize) -> usize {
149        let offset = offset.min(self.len());
150        let mut cursor = Cursor::new(self.text(), offset);
151        let mut new_offset = offset;
152        for _i in 0..count {
153            if let Some(prev_offset) = cursor.prev_grapheme() {
154                if prev_offset < limit {
155                    return new_offset;
156                }
157                new_offset = prev_offset;
158                cursor.set(prev_offset);
159            } else {
160                return new_offset;
161            }
162        }
163        new_offset
164    }
165
166    fn next_grapheme_offset(&self, offset: usize, count: usize, limit: usize) -> usize {
167        let offset = if offset > self.len() {
168            self.len()
169        } else {
170            offset
171        };
172        let mut cursor = Cursor::new(self.text(), offset);
173        let mut new_offset = offset;
174        for _i in 0..count {
175            if let Some(next_offset) = cursor.next_grapheme() {
176                if next_offset > limit {
177                    return new_offset;
178                }
179                new_offset = next_offset;
180                cursor.set(next_offset);
181            } else {
182                return new_offset;
183            }
184        }
185        new_offset
186    }
187
188    fn prev_code_boundary(&self, offset: usize) -> usize {
189        WordCursor::new(self.text(), offset).prev_code_boundary()
190    }
191
192    fn next_code_boundary(&self, offset: usize) -> usize {
193        WordCursor::new(self.text(), offset).next_code_boundary()
194    }
195
196    /// Return the previous and end boundaries of the word under cursor.
197    fn select_word(&self, offset: usize) -> (usize, usize) {
198        WordCursor::new(self.text(), offset).select_word()
199    }
200
201    /// Returns the offset of the first non-blank character on the given line.
202    /// If the line is one past the last line, then the offset at the end of the rope is returned.
203    /// If the line is further past that, then it defaults to the last line.
204    fn first_non_blank_character_on_line(&self, line: usize) -> usize {
205        let last_line = self.last_line();
206        let line = if line > last_line + 1 {
207            last_line
208        } else {
209            line
210        };
211        let line_start_offset = self.text().offset_of_line(line);
212        WordCursor::new(self.text(), line_start_offset).next_non_blank_char()
213    }
214
215    fn indent_on_line(&self, line: usize) -> String {
216        let line_start_offset = self.text().offset_of_line(line);
217        let word_boundary = WordCursor::new(self.text(), line_start_offset).next_non_blank_char();
218        let indent = self.text().slice_to_cow(line_start_offset..word_boundary);
219        indent.to_string()
220    }
221
222    /// Get the content of the rope as a [`Cow`] string, for 'nice' ranges (small, and at the right
223    /// offsets) this will be a reference to the rope's data. Otherwise, it allocates a new string.
224    /// You should be somewhat wary of requesting large parts of the rope, as it will allocate
225    /// a new string since it isn't contiguous in memory for large chunks.
226    fn slice_to_cow(&self, range: Range<usize>) -> Cow<'_, str> {
227        self.text()
228            .slice_to_cow(range.start.min(self.len())..range.end.min(self.len()))
229    }
230
231    // TODO(minor): Once you can have an `impl Trait` return type in a trait, this could use that.
232    /// Iterate over `(utf8_offset, char)` values in the given range.
233    #[allow(clippy::type_complexity)]
234    /// This uses `iter_chunks` and so does not allocate, compared to [`Self::slice_to_cow`] which can
235    fn char_indices_iter<'a, T: IntervalBounds>(
236        &'a self,
237        range: T,
238    ) -> CharIndicesJoin<
239        std::str::CharIndices<'a>,
240        std::iter::Map<ChunkIter<'a>, fn(&str) -> std::str::CharIndices<'_>>,
241    > {
242        let iter: ChunkIter<'a> = self.text().iter_chunks(range);
243        let iter: std::iter::Map<ChunkIter<'a>, fn(&str) -> std::str::CharIndices<'_>> =
244            iter.map(str::char_indices);
245        CharIndicesJoin::new(iter)
246    }
247
248    /// The number of lines in the file
249    fn num_lines(&self) -> usize {
250        self.last_line() + 1
251    }
252
253    /// The length of the given line
254    fn line_len(&self, line: usize) -> usize {
255        self.offset_of_line(line + 1) - self.offset_of_line(line)
256    }
257
258    /// Returns `true` if the given line contains no non-whitespace characters.
259    fn is_line_whitespace(&self, line: usize) -> bool {
260        let line_start_offset = self.text().offset_of_line(line);
261        let mut word_cursor = WordCursor::new(self.text(), line_start_offset);
262
263        word_cursor.next_non_blank_char();
264        let c = word_cursor.inner.next_codepoint();
265
266        match c {
267            None | Some('\n') => true,
268            Some('\r') => {
269                let c = word_cursor.inner.next_codepoint();
270                c.is_some_and(|c| c == '\n')
271            }
272            _ => false,
273        }
274    }
275
276    fn move_left(&self, offset: usize, mode: Mode, count: usize) -> usize {
277        let min_offset = if mode == Mode::Insert {
278            0
279        } else {
280            let line = self.line_of_offset(offset);
281            self.offset_of_line(line)
282        };
283
284        self.prev_grapheme_offset(offset, count, min_offset)
285    }
286
287    fn move_right(&self, offset: usize, mode: Mode, count: usize) -> usize {
288        let max_offset = if mode == Mode::Insert {
289            self.len()
290        } else {
291            self.offset_line_end(offset, mode != Mode::Normal)
292        };
293
294        self.next_grapheme_offset(offset, count, max_offset)
295    }
296
297    fn find_nth_paragraph<F>(&self, offset: usize, mut count: usize, mut find_next: F) -> usize
298    where
299        F: FnMut(&mut ParagraphCursor) -> Option<usize>,
300    {
301        let mut cursor = ParagraphCursor::new(self.text(), offset);
302        let mut new_offset = offset;
303        while count != 0 {
304            // FIXME: wait for if-let-chain
305            if let Some(offset) = find_next(&mut cursor) {
306                new_offset = offset;
307            } else {
308                break;
309            }
310            count -= 1;
311        }
312        new_offset
313    }
314
315    fn move_n_paragraphs_forward(&self, offset: usize, count: usize) -> usize {
316        self.find_nth_paragraph(offset, count, |cursor| cursor.next_boundary())
317    }
318
319    fn move_n_paragraphs_backward(&self, offset: usize, count: usize) -> usize {
320        self.find_nth_paragraph(offset, count, |cursor| cursor.prev_boundary())
321    }
322
323    /// Find the nth (`count`) word starting at `offset` in either direction
324    /// depending on `find_next`.
325    ///
326    /// A `WordCursor` is created and given to the `find_next` function for the
327    /// search.  The `find_next` function should return None when there is no
328    /// more word found.  Despite the name, `find_next` can search in either
329    /// direction.
330    fn find_nth_word<F>(&self, offset: usize, mut count: usize, mut find_next: F) -> usize
331    where
332        F: FnMut(&mut WordCursor) -> Option<usize>,
333    {
334        let mut cursor = WordCursor::new(self.text(), offset);
335        let mut new_offset = offset;
336        while count != 0 {
337            // FIXME: wait for if-let-chain
338            if let Some(offset) = find_next(&mut cursor) {
339                new_offset = offset;
340            } else {
341                break;
342            }
343            count -= 1;
344        }
345        new_offset
346    }
347
348    fn move_n_words_forward(&self, offset: usize, count: usize) -> usize {
349        self.find_nth_word(offset, count, |cursor| cursor.next_boundary())
350    }
351
352    fn move_n_wordends_forward(&self, offset: usize, count: usize, inserting: bool) -> usize {
353        let mut new_offset = self.find_nth_word(offset, count, |cursor| cursor.end_boundary());
354        if !inserting && new_offset != self.len() {
355            new_offset = self.prev_grapheme_offset(new_offset, 1, 0);
356        }
357        new_offset
358    }
359
360    fn move_n_words_backward(&self, offset: usize, count: usize, mode: Mode) -> usize {
361        self.find_nth_word(offset, count, |cursor| cursor.prev_boundary(mode))
362    }
363
364    fn move_word_backward_deletion(&self, offset: usize) -> usize {
365        self.find_nth_word(offset, 1, |cursor| cursor.prev_deletion_boundary())
366    }
367}
368
369#[derive(Clone)]
370pub struct RopeTextVal {
371    pub text: Rope,
372}
373impl RopeTextVal {
374    pub fn new(text: Rope) -> Self {
375        Self { text }
376    }
377}
378impl RopeText for RopeTextVal {
379    fn text(&self) -> &Rope {
380        &self.text
381    }
382}
383impl From<Rope> for RopeTextVal {
384    fn from(text: Rope) -> Self {
385        Self::new(text)
386    }
387}
388#[derive(Copy, Clone)]
389pub struct RopeTextRef<'a> {
390    pub text: &'a Rope,
391}
392impl<'a> RopeTextRef<'a> {
393    pub fn new(text: &'a Rope) -> Self {
394        Self { text }
395    }
396}
397impl RopeText for RopeTextRef<'_> {
398    fn text(&self) -> &Rope {
399        self.text
400    }
401}
402impl<'a> From<&'a Rope> for RopeTextRef<'a> {
403    fn from(text: &'a Rope) -> Self {
404        Self::new(text)
405    }
406}
407
408impl RopeText for Rope {
409    fn text(&self) -> &Rope {
410        self
411    }
412}
413
414/// Joins an iterator of iterators over char indices `(usize, char)` into one
415/// as if they were from a single long string
416/// Assumes the iterators end after the first `None` value
417#[derive(Clone)]
418pub struct CharIndicesJoin<I: Iterator<Item = (usize, char)>, O: Iterator<Item = I>> {
419    /// Our iterator of iterators
420    main_iter: O,
421    /// Our current working iterator of indices
422    current_indices: Option<I>,
423    /// The amount we should shift future offsets
424    current_base: usize,
425    /// The latest base, since we don't know when the `current_indices` iterator will end
426    latest_base: usize,
427}
428
429impl<I: Iterator<Item = (usize, char)>, O: Iterator<Item = I>> CharIndicesJoin<I, O> {
430    pub fn new(main_iter: O) -> CharIndicesJoin<I, O> {
431        CharIndicesJoin {
432            main_iter,
433            current_indices: None,
434            current_base: 0,
435            latest_base: 0,
436        }
437    }
438}
439
440impl<I: Iterator<Item = (usize, char)>, O: Iterator<Item = I>> Iterator for CharIndicesJoin<I, O> {
441    type Item = (usize, char);
442
443    fn next(&mut self) -> Option<Self::Item> {
444        if let Some(current) = &mut self.current_indices {
445            if let Some((next_offset, next_ch)) = current.next() {
446                // Shift by the current base offset, which is the accumulated offset from previous
447                // iterators, which makes so the offset produced looks like it is from one long str
448                let next_offset = self.current_base + next_offset;
449                // Store the latest base offset, because we don't know when the current iterator
450                // will end (though technically the str iterator impl does)
451                self.latest_base = next_offset + next_ch.len_utf8();
452                return Some((next_offset, next_ch));
453            }
454        }
455
456        // Otherwise, if we didn't return something above, then we get a next iterator
457        if let Some(next_current) = self.main_iter.next() {
458            // Update our current working iterator
459            self.current_indices = Some(next_current);
460            // Update the current base offset with the previous iterators latest offset base
461            // This is what we are shifting by
462            self.current_base = self.latest_base;
463
464            // Get the next item without new current iterator
465            // As long as main_iter and the iterators it produces aren't infinite then this
466            // recursion won't be infinite either
467            // and even the non-recursion version would be infinite if those were infinite
468            self.next()
469        } else {
470            // We didn't get anything from the main iter, so we're completely done.
471            None
472        }
473    }
474}
475
476#[cfg(test)]
477mod tests {
478    use lapce_xi_rope::Rope;
479
480    use super::RopeText;
481    use crate::buffer::rope_text::RopeTextVal;
482
483    #[test]
484    fn test_line_content() {
485        let text = Rope::from("");
486        let text = RopeTextVal::new(text);
487
488        assert_eq!(text.line_content(0), "");
489        assert_eq!(text.line_content(1), "");
490        assert_eq!(text.line_content(2), "");
491
492        let text = Rope::from("abc\ndef\nghi");
493        let text = RopeTextVal::new(text);
494
495        assert_eq!(text.line_content(0), "abc\n");
496        assert_eq!(text.line_content(1), "def\n");
497        assert_eq!(text.line_content(2), "ghi");
498        assert_eq!(text.line_content(3), "");
499        assert_eq!(text.line_content(4), "");
500        assert_eq!(text.line_content(5), "");
501
502        let text = Rope::from("abc\r\ndef\r\nghi");
503        let text = RopeTextVal::new(text);
504
505        assert_eq!(text.line_content(0), "abc\r\n");
506        assert_eq!(text.line_content(1), "def\r\n");
507        assert_eq!(text.line_content(2), "ghi");
508        assert_eq!(text.line_content(3), "");
509        assert_eq!(text.line_content(4), "");
510        assert_eq!(text.line_content(5), "");
511    }
512
513    #[test]
514    fn test_offset_of_line() {
515        let text = Rope::from("");
516        let text = RopeTextVal::new(text);
517
518        assert_eq!(text.offset_of_line(0), 0);
519        assert_eq!(text.offset_of_line(1), 0);
520        assert_eq!(text.offset_of_line(2), 0);
521
522        let text = Rope::from("abc\ndef\nghi");
523        let text = RopeTextVal::new(text);
524
525        assert_eq!(text.offset_of_line(0), 0);
526        assert_eq!(text.offset_of_line(1), 4);
527        assert_eq!(text.offset_of_line(2), 8);
528        assert_eq!(text.offset_of_line(3), text.len()); // 11
529        assert_eq!(text.offset_of_line(4), text.len());
530        assert_eq!(text.offset_of_line(5), text.len());
531
532        let text = Rope::from("abc\r\ndef\r\nghi");
533        let text = RopeTextVal::new(text);
534
535        assert_eq!(text.offset_of_line(0), 0);
536        assert_eq!(text.offset_of_line(1), 5);
537        assert_eq!(text.offset_of_line(2), 10);
538        assert_eq!(text.offset_of_line(3), text.len()); // 13
539        assert_eq!(text.offset_of_line(4), text.len());
540        assert_eq!(text.offset_of_line(5), text.len());
541    }
542
543    #[test]
544    fn test_offset_of_line_col() {
545        let text = Rope::from("abc\ndef\nghi");
546        let text = RopeTextVal::new(text);
547
548        assert_eq!(text.offset_of_line_col(0, 0), 0);
549        assert_eq!(text.offset_of_line_col(0, 1), 1);
550        assert_eq!(text.offset_of_line_col(0, 2), 2);
551        assert_eq!(text.offset_of_line_col(0, 3), 3);
552        assert_eq!(text.offset_of_line_col(0, 4), 3);
553        assert_eq!(text.offset_of_line_col(1, 0), 4);
554
555        let text = Rope::from("abc\r\ndef\r\nghi");
556        let text = RopeTextVal::new(text);
557
558        assert_eq!(text.offset_of_line_col(0, 0), 0);
559        assert_eq!(text.offset_of_line_col(0, 1), 1);
560        assert_eq!(text.offset_of_line_col(0, 2), 2);
561        assert_eq!(text.offset_of_line_col(0, 3), 3);
562        assert_eq!(text.offset_of_line_col(0, 4), 3);
563        assert_eq!(text.offset_of_line_col(1, 0), 5);
564    }
565
566    #[test]
567    fn test_line_end_offset() {
568        let text = Rope::from("");
569        let text = RopeTextVal::new(text);
570
571        assert_eq!(text.line_end_offset(0, false), 0);
572        assert_eq!(text.line_end_offset(0, true), 0);
573        assert_eq!(text.line_end_offset(1, false), 0);
574        assert_eq!(text.line_end_offset(1, true), 0);
575        assert_eq!(text.line_end_offset(2, false), 0);
576        assert_eq!(text.line_end_offset(2, true), 0);
577
578        let text = Rope::from("abc\ndef\nghi");
579        let text = RopeTextVal::new(text);
580
581        assert_eq!(text.line_end_offset(0, false), 2);
582        assert_eq!(text.line_end_offset(0, true), 3);
583        assert_eq!(text.line_end_offset(1, false), 6);
584        assert_eq!(text.line_end_offset(1, true), 7);
585        assert_eq!(text.line_end_offset(2, false), 10);
586        assert_eq!(text.line_end_offset(2, true), text.len());
587        assert_eq!(text.line_end_offset(3, false), text.len());
588        assert_eq!(text.line_end_offset(3, true), text.len());
589        assert_eq!(text.line_end_offset(4, false), text.len());
590        assert_eq!(text.line_end_offset(4, true), text.len());
591    }
592
593    #[test]
594    fn test_prev_grapheme_offset() {
595        let text = Rope::from("");
596        let text = RopeTextVal::new(text);
597
598        assert_eq!(text.prev_grapheme_offset(0, 0, 0), 0);
599        assert_eq!(text.prev_grapheme_offset(0, 1, 0), 0);
600        assert_eq!(text.prev_grapheme_offset(0, 1, 1), 0);
601
602        let text = Rope::from("abc def ghi");
603        let text = RopeTextVal::new(text);
604
605        assert_eq!(text.prev_grapheme_offset(0, 0, 0), 0);
606        assert_eq!(text.prev_grapheme_offset(0, 1, 0), 0);
607        assert_eq!(text.prev_grapheme_offset(0, 1, 1), 0);
608        assert_eq!(text.prev_grapheme_offset(2, 1, 0), 1);
609        assert_eq!(text.prev_grapheme_offset(2, 1, 1), 1);
610    }
611
612    #[test]
613    fn test_first_non_blank_character_on_line() {
614        let text = Rope::from("");
615        let text = RopeTextVal::new(text);
616
617        assert_eq!(text.first_non_blank_character_on_line(0), 0);
618        assert_eq!(text.first_non_blank_character_on_line(1), 0);
619        assert_eq!(text.first_non_blank_character_on_line(2), 0);
620
621        let text = Rope::from("abc\ndef\nghi");
622        let text = RopeTextVal::new(text);
623
624        assert_eq!(text.first_non_blank_character_on_line(0), 0);
625        assert_eq!(text.first_non_blank_character_on_line(1), 4);
626        assert_eq!(text.first_non_blank_character_on_line(2), 8);
627        assert_eq!(text.first_non_blank_character_on_line(3), 11);
628        assert_eq!(text.first_non_blank_character_on_line(4), 8);
629        assert_eq!(text.first_non_blank_character_on_line(5), 8);
630
631        let text = Rope::from("abc\r\ndef\r\nghi");
632        let text = RopeTextVal::new(text);
633
634        assert_eq!(text.first_non_blank_character_on_line(0), 0);
635        assert_eq!(text.first_non_blank_character_on_line(1), 5);
636        assert_eq!(text.first_non_blank_character_on_line(2), 10);
637        assert_eq!(text.first_non_blank_character_on_line(3), 13);
638        assert_eq!(text.first_non_blank_character_on_line(4), 10);
639        assert_eq!(text.first_non_blank_character_on_line(5), 10);
640    }
641
642    #[test]
643    fn test_is_line_whitespace() {
644        let text = Rope::from("");
645        let text = RopeTextVal::new(text);
646
647        assert!(text.is_line_whitespace(0));
648
649        let text = Rope::from("\n  \t\r\t \t  \n");
650        let text = RopeTextVal::new(text);
651
652        assert!(text.is_line_whitespace(0));
653        assert!(!text.is_line_whitespace(1));
654        assert!(text.is_line_whitespace(2));
655
656        let text = Rope::from("qwerty\n\tf\t\r\n00");
657        let text = RopeTextVal::new(text);
658
659        assert!(!text.is_line_whitespace(0));
660        assert!(!text.is_line_whitespace(1));
661        assert!(!text.is_line_whitespace(2));
662
663        let text = Rope::from("  \r#\n\t                   \r\n)\t\t\t\t\t\t\t\t");
664        let text = RopeTextVal::new(text);
665
666        assert!(!text.is_line_whitespace(0));
667        assert!(text.is_line_whitespace(1));
668        assert!(!text.is_line_whitespace(2));
669
670        let text = Rope::from("   \r\n  \r");
671        let text = RopeTextVal::new(text);
672
673        assert!(text.is_line_whitespace(0));
674        assert!(!text.is_line_whitespace(1));
675    }
676}