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 | 164 |
1 files changed, 44 insertions, 120 deletions
diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index e70cb949..ee764bc6 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -1,15 +1,9 @@ -use std::{ - borrow::Cow, - cmp::Ordering, - ops::{Add, AddAssign, Sub, SubAssign}, -}; - -use helix_stdx::rope::RopeSliceExt; +use std::{borrow::Cow, cmp::Ordering}; 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 +16,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 +51,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 +65,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) } @@ -123,7 +90,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 { @@ -154,31 +121,22 @@ pub fn visual_offset_from_block( annotations: &TextAnnotations, ) -> (Position, usize) { let mut last_pos = Position::default(); - let mut formatter = + let (formatter, block_start) = DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor); - let block_start = formatter.next_char_pos(); + let mut char_pos = block_start; + + for (grapheme, vpos) in formatter { + last_pos = vpos; + char_pos += grapheme.doc_chars(); - while let Some(grapheme) = formatter.next() { - last_pos = grapheme.visual_pos; - if formatter.next_char_pos() > pos { - return (grapheme.visual_pos, block_start); + if char_pos > pos { + return (last_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, @@ -195,21 +153,22 @@ pub fn visual_offset_from_anchor( annotations: &TextAnnotations, max_rows: usize, ) -> Result<(Position, usize), VisualOffsetError> { - let mut formatter = + let (formatter, block_start) = DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor); + let mut char_pos = block_start; 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; + for (grapheme, vpos) in formatter { + last_pos = vpos; + char_pos += grapheme.doc_chars(); - if formatter.next_char_pos() > pos { + if char_pos > pos { if let Some(anchor_line) = anchor_line { last_pos.row -= anchor_line; return Ok((last_pos, block_start)); @@ -217,7 +176,7 @@ pub fn visual_offset_from_anchor( found_pos = Some(last_pos); } } - if formatter.next_char_pos() > anchor && anchor_line.is_none() { + if 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; @@ -231,7 +190,7 @@ pub fn visual_offset_from_anchor( } if let Some(anchor_line) = anchor_line { - if grapheme.visual_pos.row >= anchor_line + max_rows { + if vpos.row >= anchor_line + max_rows { return Err(VisualOffsetError::PosAfterMaxRow); } } @@ -262,14 +221,7 @@ pub fn visual_offset_from_anchor( 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 +231,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; } @@ -310,7 +262,7 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize) let mut col_char_offset = 0; let mut cols_remaining = col; - for grapheme in text.slice(line_start..line_end).graphemes() { + for grapheme in RopeGraphemes::new(text.slice(line_start..line_end)) { let grapheme_width = if grapheme == "\t" { tab_width - ((col - cols_remaining) % tab_width) } else { @@ -416,43 +368,39 @@ pub fn char_idx_at_visual_block_offset( text_fmt: &TextFormat, annotations: &TextAnnotations, ) -> (usize, usize) { - let mut formatter = + let (formatter, mut char_idx) = 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_char_idx = char_idx; + let mut last_char_idx_on_line = None; let mut last_row = 0; - for grapheme in &mut formatter { - match grapheme.visual_pos.row.cmp(&row) { + for (grapheme, grapheme_pos) in formatter { + match grapheme_pos.row.cmp(&row) { Ordering::Equal => { - if grapheme.visual_pos.col + grapheme.width() > column { + if grapheme_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); + return (char_idx, 0); + } else if let Some(char_idx) = last_char_idx_on_line { + return (char_idx, 0); } } else if !grapheme.is_virtual() { - found_non_virtual_on_row = true; - last_char_idx = grapheme.char_idx; + last_char_idx_on_line = Some(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; - } - } + _ => (), } + + last_char_idx = char_idx; + last_row = grapheme_pos.row; + char_idx += grapheme.doc_chars(); } - (formatter.next_char_pos(), 0) + (char_idx, 0) } #[cfg(test)] mod test { use super::*; - use crate::text_annotations::InlineAnnotation; use crate::Rope; #[test] @@ -814,30 +762,6 @@ mod test { } #[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(..); |