diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 8675fc6f..7a0518d8 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -5,7 +5,7 @@ mod selection; mod state; mod transaction; -pub use ropey::Rope; +pub use ropey::{Rope, RopeSlice}; pub use tendril::StrTendril as Tendril; pub use buffer::Buffer; diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 1ffd708e..ccbfd28a 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -1,5 +1,5 @@ -use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary}; -use crate::{Buffer, Selection, SelectionRange}; +use crate::graphemes::{nth_next_grapheme_boundary, nth_prev_grapheme_boundary, RopeGraphemes}; +use crate::{Buffer, Rope, RopeSlice, Selection, SelectionRange}; /// A state represents the current editor state of a single buffer. pub struct State { @@ -117,3 +117,44 @@ impl State { // TODO: update selection in state via transaction } } + +/// Coordinates are a 0-indexed line and column pair. +type Coords = (usize, usize); // line, col + +/// Convert a character index to (line, column) coordinates. +pub fn coords_at_pos(text: &RopeSlice, pos: usize) -> Coords { + let line = text.char_to_line(pos); + let line_start = text.line_to_char(line); + let col = RopeGraphemes::new(&text.slice(line_start..pos)).count(); + (line, col) +} + +/// Convert (line, column) coordinates to a character index. +pub fn pos_at_coords(text: &RopeSlice, coords: Coords) -> usize { + let (line, col) = coords; + let line_start = text.line_to_char(line); + nth_next_grapheme_boundary(text, line_start, col) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_coords_at_pos() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + assert_eq!(coords_at_pos(&text.slice(..), 0), (0, 0)); + assert_eq!(coords_at_pos(&text.slice(..), 5), (0, 5)); // position on \n + assert_eq!(coords_at_pos(&text.slice(..), 6), (1, 0)); // position on w + assert_eq!(coords_at_pos(&text.slice(..), 11), (1, 5)); // position on d + } + + #[test] + fn test_pos_at_coords() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + assert_eq!(pos_at_coords(&text.slice(..), (0, 0)), 0); + assert_eq!(pos_at_coords(&text.slice(..), (0, 5)), 5); // position on \n + assert_eq!(pos_at_coords(&text.slice(..), (1, 0)), 6); // position on w + assert_eq!(pos_at_coords(&text.slice(..), (1, 5)), 11); // position on d + } +}