From f303a1d5fe42f07c45715ba45fea208c0284c026 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 14 May 2021 14:44:19 +0200 Subject: [PATCH] Implement `::chars`, `::to_point` and `::to_offset` for Rope --- zed/src/editor/buffer/rope.rs | 104 +++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs index 8f78c94abd..10c94ef45c 100644 --- a/zed/src/editor/buffer/rope.rs +++ b/zed/src/editor/buffer/rope.rs @@ -1,5 +1,6 @@ use super::Point; use crate::sum_tree::{self, SeekBias, SumTree}; +use anyhow::{anyhow, Result}; use arrayvec::ArrayString; use std::{cmp, ops::Range, str}; @@ -140,6 +141,28 @@ impl Rope { } text } + + fn to_point(&self, offset: usize) -> Result { + if offset <= self.summary().chars { + let mut cursor = self.chunks.cursor::(); + cursor.seek(&offset, SeekBias::Left, &()); + let overshoot = offset - cursor.start().chars; + Ok(cursor.start().lines + cursor.item().unwrap().to_point(overshoot)) + } else { + Err(anyhow!("offset out of bounds")) + } + } + + fn to_offset(&self, point: Point) -> Result { + if point <= self.summary().lines { + let mut cursor = self.chunks.cursor::(); + cursor.seek(&point, SeekBias::Left, &()); + let overshoot = point - cursor.start().lines; + Ok(cursor.start().chars + cursor.item().unwrap().to_offset(overshoot)) + } else { + Err(anyhow!("offset out of bounds")) + } + } } impl<'a> From<&'a str> for Rope { @@ -153,6 +176,46 @@ impl<'a> From<&'a str> for Rope { #[derive(Clone, Debug, Default)] struct Chunk(ArrayString<[u8; 2 * CHUNK_BASE]>); +impl Chunk { + fn to_point(&self, target: usize) -> Point { + let mut offset = 0; + let mut point = Point::new(0, 0); + for ch in self.0.chars() { + if offset >= target { + break; + } + + if ch == '\n' { + point.row += 1; + point.column = 0; + } else { + point.column += 1; + } + offset += 1; + } + point + } + + fn to_offset(&self, target: Point) -> usize { + let mut offset = 0; + let mut point = Point::new(0, 0); + for ch in self.0.chars() { + if point >= target { + break; + } + + if ch == '\n' { + point.row += 1; + point.column = 0; + } else { + point.column += 1; + } + offset += 1; + } + offset + } +} + impl sum_tree::Item for Chunk { type Summary = TextSummary; @@ -232,12 +295,24 @@ impl std::ops::AddAssign for TextSummary { } } +impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary { + fn add_summary(&mut self, summary: &'a TextSummary) { + *self += summary; + } +} + impl<'a> sum_tree::Dimension<'a, TextSummary> for usize { fn add_summary(&mut self, summary: &'a TextSummary) { *self += summary.chars; } } +impl<'a> sum_tree::Dimension<'a, TextSummary> for Point { + fn add_summary(&mut self, summary: &'a TextSummary) { + *self += &summary.lines; + } +} + pub struct Chars<'a> { cursor: sum_tree::Cursor<'a, Chunk, usize, usize>, chars: str::Chars<'a>, @@ -247,8 +322,14 @@ impl<'a> Chars<'a> { pub fn new(rope: &'a Rope, start: usize) -> Self { let mut cursor = rope.chunks.cursor::(); cursor.slice(&start, SeekBias::Left, &()); - let chunk = cursor.item().expect("invalid index"); - let chars = chunk.0[start - cursor.start()..].chars(); + let chars = if let Some(chunk) = cursor.item() { + let ix = start - cursor.start(); + cursor.next(); + chunk.0[ix..].chars() + } else { + "".chars() + }; + Self { cursor, chars } } } @@ -316,6 +397,25 @@ mod tests { expected = new_expected; assert_eq!(actual.text(), expected); + + for _ in 0..5 { + let ix = rng.gen_range(0..=expected.len()); + assert_eq!(actual.chars_at(ix).collect::(), expected[ix..]); + } + + let mut point = Point::new(0, 0); + let mut offset = 0; + for ch in expected.chars() { + assert_eq!(actual.to_point(offset).unwrap(), point); + assert_eq!(actual.to_offset(point).unwrap(), offset); + if ch == '\n' { + point.row += 1; + point.column = 0 + } else { + point.column += 1; + } + offset += 1; + } } } }