Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/position.rs')
| -rw-r--r-- | helix-core/src/position.rs | 655 |
1 files changed, 14 insertions, 641 deletions
diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index e70cb949..ce37300a 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -1,17 +1,9 @@ -use std::{ - borrow::Cow, - cmp::Ordering, - ops::{Add, AddAssign, Sub, SubAssign}, -}; - -use helix_stdx::rope::RopeSliceExt; +use std::borrow::Cow; use crate::{ chars::char_is_line_ending, - doc_formatter::{DocumentFormatter, TextFormat}, - graphemes::{ensure_grapheme_boundary_prev, grapheme_width}, + graphemes::{ensure_grapheme_boundary_prev, grapheme_width, RopeGraphemes}, line_ending::line_end_char_index, - text_annotations::TextAnnotations, RopeSlice, }; @@ -22,38 +14,6 @@ pub struct Position { pub col: usize, } -impl AddAssign for Position { - fn add_assign(&mut self, rhs: Self) { - self.row += rhs.row; - self.col += rhs.col; - } -} - -impl SubAssign for Position { - fn sub_assign(&mut self, rhs: Self) { - self.row -= rhs.row; - self.col -= rhs.col; - } -} - -impl Sub for Position { - type Output = Position; - - fn sub(mut self, rhs: Self) -> Self::Output { - self -= rhs; - self - } -} - -impl Add for Position { - type Output = Position; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - impl Position { pub const fn new(row: usize, col: usize) -> Self { Self { row, col } @@ -89,6 +49,11 @@ impl From<(usize, usize)> for Position { } } +impl From<Position> for tree_sitter::Point { + fn from(pos: Position) -> Self { + Self::new(pos.row, pos.col) + } +} /// Convert a character index to (line, column) coordinates. /// /// column in `char` count which can be used for row:column display in @@ -98,7 +63,7 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { let line_start = text.line_to_char(line); let pos = ensure_grapheme_boundary_prev(text, pos); - let col = text.slice(line_start..pos).graphemes().count(); + let col = RopeGraphemes::new(text.slice(line_start..pos)).count(); Position::new(line, col) } @@ -108,13 +73,6 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position { /// Takes \t, double-width characters (CJK) into account as well as text /// not in the document in the future. /// See [`coords_at_pos`] for an "objective" one. -/// -/// This function should be used very rarely. Usually `visual_offset_from_anchor` -/// or `visual_offset_from_block` is preferable. However when you want to compute the -/// actual visual row/column in the text (not what is actually shown on screen) -/// then you should use this function. For example aligning text should ignore virtual -/// text and softwrap. -#[deprecated = "Doesn't account for softwrap or decorations, use visual_offset_from_anchor instead"] pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position { let line = text.char_to_line(pos); @@ -123,7 +81,7 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po let mut col = 0; - for grapheme in text.slice(line_start..pos).graphemes() { + for grapheme in RopeGraphemes::new(text.slice(line_start..pos)) { if grapheme == "\t" { col += tab_width - (col % tab_width); } else { @@ -135,114 +93,6 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po Position::new(line, col) } -/// Returns the visual offset from the start of the first visual line -/// in the block that contains anchor. -/// Text is always wrapped at blocks, they usually correspond to -/// actual line breaks but for very long lines -/// softwrapping positions are estimated with an O(1) algorithm -/// to ensure consistent performance for large lines (currently unimplemented) -/// -/// Usually you want to use `visual_offset_from_anchor` instead but this function -/// can be useful (and faster) if -/// * You already know the visual position of the block -/// * You only care about the horizontal offset (column) and not the vertical offset (row) -pub fn visual_offset_from_block( - text: RopeSlice, - anchor: usize, - pos: usize, - text_fmt: &TextFormat, - annotations: &TextAnnotations, -) -> (Position, usize) { - let mut last_pos = Position::default(); - let mut formatter = - DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor); - let block_start = formatter.next_char_pos(); - - while let Some(grapheme) = formatter.next() { - last_pos = grapheme.visual_pos; - if formatter.next_char_pos() > pos { - return (grapheme.visual_pos, block_start); - } - } - - (last_pos, block_start) -} - -/// Returns the height of the given text when softwrapping -pub fn softwrapped_dimensions(text: RopeSlice, text_fmt: &TextFormat) -> (usize, u16) { - let last_pos = - visual_offset_from_block(text, 0, usize::MAX, text_fmt, &TextAnnotations::default()).0; - if last_pos.row == 0 { - (1, last_pos.col as u16) - } else { - (last_pos.row + 1, text_fmt.viewport_width) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum VisualOffsetError { - PosBeforeAnchorRow, - PosAfterMaxRow, -} - -/// Returns the visual offset from the start of the visual line -/// that contains anchor. -pub fn visual_offset_from_anchor( - text: RopeSlice, - anchor: usize, - pos: usize, - text_fmt: &TextFormat, - annotations: &TextAnnotations, - max_rows: usize, -) -> Result<(Position, usize), VisualOffsetError> { - let mut formatter = - DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor); - let mut anchor_line = None; - let mut found_pos = None; - let mut last_pos = Position::default(); - - let block_start = formatter.next_char_pos(); - if pos < block_start { - return Err(VisualOffsetError::PosBeforeAnchorRow); - } - - while let Some(grapheme) = formatter.next() { - last_pos = grapheme.visual_pos; - - if formatter.next_char_pos() > pos { - if let Some(anchor_line) = anchor_line { - last_pos.row -= anchor_line; - return Ok((last_pos, block_start)); - } else { - found_pos = Some(last_pos); - } - } - if formatter.next_char_pos() > anchor && anchor_line.is_none() { - if let Some(mut found_pos) = found_pos { - return if found_pos.row == last_pos.row { - found_pos.row = 0; - Ok((found_pos, block_start)) - } else { - Err(VisualOffsetError::PosBeforeAnchorRow) - }; - } else { - anchor_line = Some(last_pos.row); - } - } - - if let Some(anchor_line) = anchor_line { - if grapheme.visual_pos.row >= anchor_line + max_rows { - return Err(VisualOffsetError::PosAfterMaxRow); - } - } - } - - let anchor_line = anchor_line.unwrap_or(last_pos.row); - last_pos.row -= anchor_line; - - Ok((last_pos, block_start)) -} - /// Convert (line, column) coordinates to a character index. /// /// If the `line` coordinate is beyond the end of the file, the EOF @@ -259,17 +109,13 @@ pub fn visual_offset_from_anchor( /// with left-side block-cursor positions, as this prevents the the block cursor /// from jumping to the next line. Otherwise you typically want it to be `false`, /// such as when dealing with raw anchor/head positions. +/// +/// TODO: this should be changed to work in terms of visual row/column, not +/// graphemes. pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending: bool) -> usize { let Position { mut row, col } = coords; if limit_before_line_ending { - let lines = text.len_lines() - 1; - - row = row.min(if crate::line_ending::get_line_ending(&text).is_some() { - // if the last line is empty, don't jump to it - lines - 1 - } else { - lines - }); + row = row.min(text.len_lines() - 1); }; let line_start = text.line_to_char(row); let line_end = if limit_before_line_ending { @@ -279,7 +125,7 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending }; let mut col_char_offset = 0; - for (i, g) in text.slice(line_start..line_end).graphemes().enumerate() { + for (i, g) in RopeGraphemes::new(text.slice(line_start..line_end)).enumerate() { if i == col { break; } @@ -289,170 +135,9 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending line_start + col_char_offset } -/// Convert visual (line, column) coordinates to a character index. -/// -/// If the `line` coordinate is beyond the end of the file, the EOF -/// position will be returned. -/// -/// If the `column` coordinate is past the end of the given line, the -/// line-end position (in this case, just before the line ending -/// character) will be returned. -/// This function should be used very rarely. Usually `char_idx_at_visual_offset` is preferable. -/// However when you want to compute a char position from the visual row/column in the text -/// (not what is actually shown on screen) then you should use this function. -/// For example aligning text should ignore virtual text and softwrap. -#[deprecated = "Doesn't account for softwrap or decorations, use char_idx_at_visual_offset instead"] -pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize) -> usize { - let Position { mut row, col } = coords; - row = row.min(text.len_lines() - 1); - let line_start = text.line_to_char(row); - let line_end = line_end_char_index(&text, row); - - let mut col_char_offset = 0; - let mut cols_remaining = col; - for grapheme in text.slice(line_start..line_end).graphemes() { - let grapheme_width = if grapheme == "\t" { - tab_width - ((col - cols_remaining) % tab_width) - } else { - let grapheme = Cow::from(grapheme); - grapheme_width(&grapheme) - }; - - // If pos is in the middle of a wider grapheme (tab for example) - // return the starting offset. - if grapheme_width > cols_remaining { - break; - } - - cols_remaining -= grapheme_width; - col_char_offset += grapheme.chars().count(); - } - - line_start + col_char_offset -} - -/// Returns the char index on the visual line `row_offset` below the visual line of -/// the provided char index `anchor` that is closest to the supplied visual `column`. -/// -/// If the targeted visual line is entirely covered by virtual text the last -/// char position before the virtual text and a virtual offset is returned instead. -/// -/// If no (text) grapheme starts at exactly at the specified column the -/// start of the grapheme to the left is returned. If there is no grapheme -/// to the left (for example if the line starts with virtual text) then the positioning -/// of the next grapheme to the right is returned. -/// -/// If the `line` coordinate is beyond the end of the file, the EOF -/// position will be returned. -/// -/// If the `column` coordinate is past the end of the given line, the -/// line-end position (in this case, just before the line ending -/// character) will be returned. -/// -/// # Returns -/// -/// `(real_char_idx, virtual_lines)` -/// -/// The nearest character idx "closest" (see above) to the specified visual offset -/// on the visual line is returned if the visual line contains any text: -/// If the visual line at the specified offset is a virtual line generated by a `LineAnnotation` -/// the previous char_index is returned, together with the remaining vertical offset (`virtual_lines`) -pub fn char_idx_at_visual_offset( - text: RopeSlice, - mut anchor: usize, - mut row_offset: isize, - column: usize, - text_fmt: &TextFormat, - annotations: &TextAnnotations, -) -> (usize, usize) { - let mut pos = anchor; - // convert row relative to visual line containing anchor to row relative to a block containing anchor (anchor may change) - loop { - let (visual_pos_in_block, block_char_offset) = - visual_offset_from_block(text, anchor, pos, text_fmt, annotations); - row_offset += visual_pos_in_block.row as isize; - anchor = block_char_offset; - if row_offset >= 0 { - break; - } - - if block_char_offset == 0 { - row_offset = 0; - break; - } - // the row_offset is negative so we need to look at the previous block - // set the anchor to the last char before the current block so that we can compute - // the distance of this block from the start of the previous block - pos = anchor; - anchor -= 1; - } - - char_idx_at_visual_block_offset( - text, - anchor, - row_offset as usize, - column, - text_fmt, - annotations, - ) -} - -/// This function behaves the same as `char_idx_at_visual_offset`, except that -/// the vertical offset `row` is always computed relative to the block that contains `anchor` -/// instead of the visual line that contains `anchor`. -/// Usually `char_idx_at_visual_offset` is more useful but this function can be -/// used in some situations as an optimization when `visual_offset_from_block` was used -/// -/// # Returns -/// -/// `(real_char_idx, virtual_lines)` -/// -/// See `char_idx_at_visual_offset` for details -pub fn char_idx_at_visual_block_offset( - text: RopeSlice, - anchor: usize, - row: usize, - column: usize, - text_fmt: &TextFormat, - annotations: &TextAnnotations, -) -> (usize, usize) { - let mut formatter = - DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor); - let mut last_char_idx = formatter.next_char_pos(); - let mut found_non_virtual_on_row = false; - let mut last_row = 0; - for grapheme in &mut formatter { - match grapheme.visual_pos.row.cmp(&row) { - Ordering::Equal => { - if grapheme.visual_pos.col + grapheme.width() > column { - if !grapheme.is_virtual() { - return (grapheme.char_idx, 0); - } else if found_non_virtual_on_row { - return (last_char_idx, 0); - } - } else if !grapheme.is_virtual() { - found_non_virtual_on_row = true; - last_char_idx = grapheme.char_idx; - } - } - Ordering::Greater if found_non_virtual_on_row => return (last_char_idx, 0), - Ordering::Greater => return (last_char_idx, row - last_row), - Ordering::Less => { - if !grapheme.is_virtual() { - last_row = grapheme.visual_pos.row; - last_char_idx = grapheme.char_idx; - } - } - } - } - - (formatter.next_char_pos(), 0) -} - #[cfg(test)] mod test { use super::*; - use crate::text_annotations::InlineAnnotation; use crate::Rope; #[test] @@ -509,7 +194,6 @@ mod test { } #[test] - #[allow(deprecated)] fn test_visual_coords_at_pos() { let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); let slice = text.slice(..); @@ -558,130 +242,6 @@ mod test { } #[test] - fn test_visual_off_from_block() { - let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); - let slice = text.slice(..); - let annot = TextAnnotations::default(); - let text_fmt = TextFormat::default(); - assert_eq!( - visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0, - (0, 0).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0, - (0, 5).into() - ); // position on \n - assert_eq!( - visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0, - (1, 0).into() - ); // position on w - assert_eq!( - visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0, - (1, 1).into() - ); // position on o - assert_eq!( - visual_offset_from_block(slice, 0, 10, &text_fmt, &annot).0, - (1, 4).into() - ); // position on d - - // Test with wide characters. - let text = Rope::from("今日はいい\n"); - let slice = text.slice(..); - assert_eq!( - visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0, - (0, 0).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0, - (0, 2).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0, - (0, 4).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0, - (0, 6).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0, - (0, 8).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0, - (0, 10).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0, - (1, 0).into() - ); - - // Test with grapheme clusters. - let text = Rope::from("a̐éö̲\r\n"); - let slice = text.slice(..); - assert_eq!( - visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0, - (0, 0).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0, - (0, 1).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0, - (0, 2).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0, - (0, 3).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 9, &text_fmt, &annot).0, - (1, 0).into() - ); - - // Test with wide-character grapheme clusters. - // TODO: account for cluster. - let text = Rope::from("किमपि\n"); - let slice = text.slice(..); - assert_eq!( - visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0, - (0, 0).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0, - (0, 2).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0, - (0, 3).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0, - (0, 5).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0, - (1, 0).into() - ); - - // Test with tabs. - let text = Rope::from("\tHello\n"); - let slice = text.slice(..); - assert_eq!( - visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0, - (0, 0).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0, - (0, 4).into() - ); - assert_eq!( - visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0, - (0, 5).into() - ); - } - #[test] fn test_pos_at_coords() { let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); let slice = text.slice(..); @@ -745,191 +305,4 @@ mod test { assert_eq!(pos_at_coords(slice, (0, 10).into(), true), 0); assert_eq!(pos_at_coords(slice, (10, 10).into(), true), 0); } - - #[test] - #[allow(deprecated)] - fn test_pos_at_visual_coords() { - let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 5); // position on \n - assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 5); // position after \n - assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6); // position on w - assert_eq!(pos_at_visual_coords(slice, (1, 1).into(), 4), 7); // position on o - assert_eq!(pos_at_visual_coords(slice, (1, 4).into(), 4), 10); // position on d - - // Test with wide characters. - let text = Rope::from("今日はいい\n"); - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 1); - assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 1); - assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 2); - assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2); - assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 3); - assert_eq!(pos_at_visual_coords(slice, (0, 7).into(), 4), 3); - assert_eq!(pos_at_visual_coords(slice, (0, 8).into(), 4), 4); - assert_eq!(pos_at_visual_coords(slice, (0, 9).into(), 4), 4); - // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, false), 5); - // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, true), 5); - assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6); - - // Test with grapheme clusters. - let text = Rope::from("a̐éö̲\r\n"); - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 2); - assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 4); - assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 7); // \r\n is one char here - assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 7); - assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 9); - - // Test with wide-character grapheme clusters. - let text = Rope::from("किमपि"); - // 2 - 1 - 2 codepoints - // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454 - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 2); - assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 3); - - // Test with tabs. - let text = Rope::from("\tHello\n"); - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 1); - assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2); - - // Test out of bounds. - let text = Rope::new(); - let slice = text.slice(..); - assert_eq!(pos_at_visual_coords(slice, (10, 0).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4), 0); - assert_eq!(pos_at_visual_coords(slice, (10, 10).into(), 4), 0); - } - - #[test] - fn test_char_idx_at_visual_row_offset_inline_annotation() { - let text = Rope::from("foo\nbar"); - let slice = text.slice(..); - let mut text_fmt = TextFormat::default(); - let annotations = [InlineAnnotation { - text: "x".repeat(100).into(), - char_idx: 3, - }]; - text_fmt.soft_wrap = true; - - assert_eq!( - char_idx_at_visual_offset( - slice, - 0, - 1, - 0, - &text_fmt, - TextAnnotations::default().add_inline_annotations(&annotations, None) - ), - (2, 1) - ); - } - - #[test] - fn test_char_idx_at_visual_row_offset() { - let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ\nfoo"); - let slice = text.slice(..); - let mut text_fmt = TextFormat::default(); - for i in 0isize..3isize { - for j in -2isize..=2isize { - if !(0..3).contains(&(i + j)) { - continue; - } - println!("{i} {j}"); - assert_eq!( - char_idx_at_visual_offset( - slice, - slice.line_to_char(i as usize), - j, - 3, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - slice.line_to_char((i + j) as usize) + 3 - ); - } - } - - text_fmt.soft_wrap = true; - let mut softwrapped_text = "foo ".repeat(10); - softwrapped_text.push('\n'); - let last_char = softwrapped_text.len() - 1; - - let text = Rope::from(softwrapped_text.repeat(3)); - let slice = text.slice(..); - assert_eq!( - char_idx_at_visual_offset( - slice, - last_char, - 0, - 0, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - 32 - ); - assert_eq!( - char_idx_at_visual_offset( - slice, - last_char, - -1, - 0, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - 16 - ); - assert_eq!( - char_idx_at_visual_offset( - slice, - last_char, - -2, - 0, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - 0 - ); - assert_eq!( - char_idx_at_visual_offset( - slice, - softwrapped_text.len() + last_char, - -2, - 0, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - softwrapped_text.len() - ); - - assert_eq!( - char_idx_at_visual_offset( - slice, - softwrapped_text.len() + last_char, - -5, - 0, - &text_fmt, - &TextAnnotations::default(), - ) - .0, - 0 - ); - } } |