Merge pull request #61 from zed-industries/ropes-2

Store buffer's visible and deleted text using ropes
This commit is contained in:
Max Brunsfeld
2021-05-14 15:09:07 -07:00
committed by GitHub
7 changed files with 938 additions and 1021 deletions
File diff suppressed because it is too large Load Diff
+477
View File
@@ -0,0 +1,477 @@
use super::Point;
use crate::sum_tree::{self, SeekBias, SumTree};
use anyhow::{anyhow, Result};
use arrayvec::ArrayString;
use smallvec::SmallVec;
use std::{cmp, ops::Range, str};
#[cfg(test)]
const CHUNK_BASE: usize = 2;
#[cfg(not(test))]
const CHUNK_BASE: usize = 16;
#[derive(Clone, Default, Debug)]
pub struct Rope {
chunks: SumTree<Chunk>,
}
impl Rope {
pub fn new() -> Self {
Self::default()
}
pub fn append(&mut self, rope: Rope) {
let mut chunks = rope.chunks.cursor::<(), ()>();
chunks.next();
if let Some(chunk) = chunks.item() {
self.push(&chunk.0);
chunks.next();
}
self.chunks.push_tree(chunks.suffix(&()), &());
self.check_invariants();
}
pub fn push(&mut self, text: &str) {
let mut new_chunks = SmallVec::<[_; 16]>::new();
let mut new_chunk = ArrayString::new();
for ch in text.chars() {
if new_chunk.len() + ch.len_utf8() > 2 * CHUNK_BASE {
new_chunks.push(Chunk(new_chunk));
new_chunk = ArrayString::new();
}
new_chunk.push(ch);
}
if !new_chunk.is_empty() {
new_chunks.push(Chunk(new_chunk));
}
let mut new_chunks = new_chunks.into_iter();
let mut first_new_chunk = new_chunks.next();
self.chunks.update_last(
|last_chunk| {
if let Some(first_new_chunk_ref) = first_new_chunk.as_mut() {
if last_chunk.0.len() + first_new_chunk_ref.0.len() <= 2 * CHUNK_BASE {
last_chunk.0.push_str(&first_new_chunk.take().unwrap().0);
} else {
let mut text = ArrayString::<[_; 4 * CHUNK_BASE]>::new();
text.push_str(&last_chunk.0);
text.push_str(&first_new_chunk_ref.0);
let mut midpoint = text.len() / 2;
while !text.is_char_boundary(midpoint) {
midpoint += 1;
}
let (left, right) = text.split_at(midpoint);
last_chunk.0.clear();
last_chunk.0.push_str(left);
first_new_chunk_ref.0.clear();
first_new_chunk_ref.0.push_str(right);
}
}
},
&(),
);
self.chunks
.extend(first_new_chunk.into_iter().chain(new_chunks), &());
self.check_invariants();
}
fn check_invariants(&self) {
#[cfg(test)]
{
// Ensure all chunks except maybe the last one are not underflowing.
let mut chunks = self.chunks.cursor::<(), ()>().peekable();
while let Some(chunk) = chunks.next() {
if chunks.peek().is_some() {
assert!(chunk.0.len() >= CHUNK_BASE);
}
}
}
}
pub fn slice(&self, range: Range<usize>) -> Rope {
self.cursor(range.start).slice(range.end)
}
pub fn summary(&self) -> TextSummary {
self.chunks.summary()
}
pub fn len(&self) -> usize {
self.chunks.extent()
}
pub fn max_point(&self) -> Point {
self.chunks.extent()
}
pub fn cursor(&self, offset: usize) -> Cursor {
Cursor::new(self, offset)
}
pub fn chars(&self) -> Chars {
self.chars_at(0)
}
pub fn chars_at(&self, start: usize) -> Chars {
Chars::new(self, start)
}
pub fn chunks<'a>(&'a self) -> impl Iterator<Item = &'a str> {
self.chunks.cursor::<(), ()>().map(|c| c.0.as_str())
}
pub fn to_point(&self, offset: usize) -> Result<Point> {
if offset <= self.summary().chars {
let mut cursor = self.chunks.cursor::<usize, TextSummary>();
cursor.seek(&offset, SeekBias::Left, &());
let overshoot = offset - cursor.start().chars;
Ok(cursor.start().lines
+ cursor
.item()
.map_or(Point::zero(), |chunk| chunk.to_point(overshoot)))
} else {
Err(anyhow!("offset out of bounds"))
}
}
pub fn to_offset(&self, point: Point) -> Result<usize> {
// TODO: Verify the point actually exists.
if point <= self.summary().lines {
let mut cursor = self.chunks.cursor::<Point, TextSummary>();
cursor.seek(&point, SeekBias::Left, &());
let overshoot = point - cursor.start().lines;
Ok(cursor.start().chars + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)))
} else {
Err(anyhow!("offset out of bounds"))
}
}
}
impl<'a> From<&'a str> for Rope {
fn from(text: &'a str) -> Self {
let mut rope = Self::new();
rope.push(text);
rope
}
}
pub struct Cursor<'a> {
rope: &'a Rope,
chunks: sum_tree::Cursor<'a, Chunk, usize, usize>,
offset: usize,
}
impl<'a> Cursor<'a> {
pub fn new(rope: &'a Rope, offset: usize) -> Self {
let mut chunks = rope.chunks.cursor();
chunks.seek(&offset, SeekBias::Right, &());
Self {
rope,
chunks,
offset,
}
}
pub fn seek_forward(&mut self, end_offset: usize) {
debug_assert!(end_offset >= self.offset);
self.chunks.seek_forward(&end_offset, SeekBias::Right, &());
self.offset = end_offset;
}
pub fn slice(&mut self, end_offset: usize) -> Rope {
debug_assert!(end_offset >= self.offset);
let mut slice = Rope::new();
if let Some(start_chunk) = self.chunks.item() {
let start_ix = self.offset - self.chunks.start();
let end_ix = cmp::min(end_offset, self.chunks.end()) - self.chunks.start();
slice.push(&start_chunk.0[start_ix..end_ix]);
}
if end_offset > self.chunks.end() {
self.chunks.next();
slice.append(Rope {
chunks: self.chunks.slice(&end_offset, SeekBias::Right, &()),
});
if let Some(end_chunk) = self.chunks.item() {
slice.push(&end_chunk.0[..end_offset - self.chunks.start()]);
}
}
self.offset = end_offset;
slice
}
pub fn suffix(mut self) -> Rope {
self.slice(self.rope.chunks.extent())
}
pub fn offset(&self) -> usize {
self.offset
}
}
#[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;
fn summary(&self) -> Self::Summary {
let mut chars = 0;
let mut bytes = 0;
let mut lines = Point::new(0, 0);
let mut first_line_len = 0;
let mut rightmost_point = Point::new(0, 0);
for c in self.0.chars() {
chars += 1;
bytes += c.len_utf8();
if c == '\n' {
lines.row += 1;
lines.column = 0;
} else {
lines.column += 1;
if lines.row == 0 {
first_line_len = lines.column;
}
if lines.column > rightmost_point.column {
rightmost_point = lines;
}
}
}
TextSummary {
chars,
bytes,
lines,
first_line_len,
rightmost_point,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TextSummary {
pub chars: usize,
pub bytes: usize,
pub lines: Point,
pub first_line_len: u32,
pub rightmost_point: Point,
}
impl sum_tree::Summary for TextSummary {
type Context = ();
fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
*self += summary;
}
}
impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
fn add_assign(&mut self, other: &'a Self) {
let joined_line_len = self.lines.column + other.first_line_len;
if joined_line_len > self.rightmost_point.column {
self.rightmost_point = Point::new(self.lines.row, joined_line_len);
}
if other.rightmost_point.column > self.rightmost_point.column {
self.rightmost_point = self.lines + &other.rightmost_point;
}
if self.lines.row == 0 {
self.first_line_len += other.first_line_len;
}
self.chars += other.chars;
self.bytes += other.bytes;
self.lines += &other.lines;
}
}
impl std::ops::AddAssign<Self> for TextSummary {
fn add_assign(&mut self, other: Self) {
*self += &other;
}
}
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>,
}
impl<'a> Chars<'a> {
pub fn new(rope: &'a Rope, start: usize) -> Self {
let mut cursor = rope.chunks.cursor::<usize, usize>();
cursor.slice(&start, SeekBias::Left, &());
let chars = if let Some(chunk) = cursor.item() {
let ix = start - cursor.start();
cursor.next();
chunk.0[ix..].chars()
} else {
"".chars()
};
Self { cursor, chars }
}
}
impl<'a> Iterator for Chars<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ch) = self.chars.next() {
Some(ch)
} else if let Some(chunk) = self.cursor.item() {
self.chars = chunk.0.chars();
self.cursor.next();
Some(self.chars.next().unwrap())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use crate::util::RandomCharIter;
use super::*;
use rand::prelude::*;
use std::env;
#[test]
fn test_random() {
let iterations = env::var("ITERATIONS")
.map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
.unwrap_or(100);
let operations = env::var("OPERATIONS")
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
let seed_range = if let Ok(seed) = env::var("SEED") {
let seed = seed.parse().expect("invalid `SEED` variable");
seed..seed + 1
} else {
0..iterations
};
for seed in seed_range {
dbg!(seed);
let mut rng = StdRng::seed_from_u64(seed);
let mut expected = String::new();
let mut actual = Rope::new();
for _ in 0..operations {
let end_ix = rng.gen_range(0..=expected.len());
let start_ix = rng.gen_range(0..=end_ix);
let len = rng.gen_range(0..=20);
let new_text: String = RandomCharIter::new(&mut rng).take(len).collect();
let mut new_actual = Rope::new();
let mut cursor = actual.cursor(0);
new_actual.append(cursor.slice(start_ix));
new_actual.push(&new_text);
cursor.seek_forward(end_ix);
new_actual.append(cursor.suffix());
actual = new_actual;
let mut new_expected = String::new();
new_expected.push_str(&expected[..start_ix]);
new_expected.push_str(&new_text);
new_expected.push_str(&expected[end_ix..]);
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::<String>(), 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;
}
}
}
}
impl Rope {
fn text(&self) -> String {
let mut text = String::new();
for chunk in self.chunks.cursor::<(), ()>() {
text.push_str(&chunk.0);
}
text
}
}
}
-461
View File
@@ -1,461 +0,0 @@
use super::Point;
use crate::sum_tree::{self, SeekBias, SumTree};
use arrayvec::ArrayVec;
use std::{
cmp,
fmt::{self, Debug},
ops::{Bound, Index, Range, RangeBounds},
sync::Arc,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Run {
Newline,
Chars { len: usize, char_size: u8 },
}
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
struct ByteOffset(usize);
impl sum_tree::Item for Run {
type Summary = TextSummary;
fn summary(&self) -> Self::Summary {
match *self {
Run::Newline => TextSummary {
chars: 1,
bytes: 1,
lines: Point::new(1, 0),
first_line_len: 0,
rightmost_point: Point::new(0, 0),
},
Run::Chars { len, char_size } => TextSummary {
chars: len,
bytes: len * char_size as usize,
lines: Point::new(0, len as u32),
first_line_len: len as u32,
rightmost_point: Point::new(0, len as u32),
},
}
}
}
impl Run {
fn char_size(&self) -> u8 {
match self {
Run::Newline => 1,
Run::Chars { char_size, .. } => *char_size,
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TextSummary {
pub chars: usize,
pub bytes: usize,
pub lines: Point,
pub first_line_len: u32,
pub rightmost_point: Point,
}
impl sum_tree::Summary for TextSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &()) {
*self += other;
}
}
impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
fn add_assign(&mut self, other: &'a Self) {
let joined_line_len = self.lines.column + other.first_line_len;
if joined_line_len > self.rightmost_point.column {
self.rightmost_point = Point::new(self.lines.row, joined_line_len);
}
if other.rightmost_point.column > self.rightmost_point.column {
self.rightmost_point = self.lines + &other.rightmost_point;
}
if self.lines.row == 0 {
self.first_line_len += other.first_line_len;
}
self.chars += other.chars;
self.bytes += other.bytes;
self.lines += &other.lines;
}
}
impl std::ops::AddAssign<Self> for TextSummary {
fn add_assign(&mut self, other: Self) {
*self += &other;
}
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
fn add_summary(&mut self, other: &TextSummary) {
*self += other;
}
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
fn add_summary(&mut self, summary: &TextSummary) {
*self += &summary.lines;
}
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for ByteOffset {
fn add_summary(&mut self, summary: &TextSummary) {
self.0 += summary.bytes
}
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
fn add_summary(&mut self, summary: &TextSummary) {
*self += summary.chars;
}
}
#[derive(Clone)]
pub struct Text {
text: Arc<str>,
runs: SumTree<Run>,
range: Range<usize>,
}
impl From<String> for Text {
fn from(text: String) -> Self {
Self::from(Arc::from(text))
}
}
impl<'a> From<&'a str> for Text {
fn from(text: &'a str) -> Self {
Self::from(Arc::from(text))
}
}
impl From<Arc<str>> for Text {
fn from(text: Arc<str>) -> Self {
let mut runs = Vec::new();
let mut chars_len = 0;
let mut run_char_size = 0;
let mut run_chars = 0;
let mut chars = text.chars();
loop {
let ch = chars.next();
let ch_size = ch.map_or(0, |ch| ch.len_utf8());
if run_chars != 0 && (ch.is_none() || ch == Some('\n') || run_char_size != ch_size) {
runs.push(Run::Chars {
len: run_chars,
char_size: run_char_size as u8,
});
run_chars = 0;
}
run_char_size = ch_size;
match ch {
Some('\n') => runs.push(Run::Newline),
Some(_) => run_chars += 1,
None => break,
}
chars_len += 1;
}
let mut tree = SumTree::new();
tree.extend(runs, &());
Text {
text,
runs: tree,
range: 0..chars_len,
}
}
}
impl Debug for Text {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Text").field(&self.as_str()).finish()
}
}
impl PartialEq for Text {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
}
}
impl Eq for Text {}
impl<T: RangeBounds<usize>> Index<T> for Text {
type Output = str;
fn index(&self, range: T) -> &Self::Output {
let start = match range.start_bound() {
Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
Bound::Excluded(_) => unimplemented!(),
Bound::Unbounded => self.range.start,
};
let end = match range.end_bound() {
Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
Bound::Unbounded => self.range.end,
};
let byte_start = self.abs_byte_offset_for_offset(start);
let byte_end = self.abs_byte_offset_for_offset(end);
&self.text[byte_start..byte_end]
}
}
impl Text {
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
pub fn as_str(&self) -> &str {
&self[..]
}
pub fn slice<T: RangeBounds<usize>>(&self, range: T) -> Text {
let start = match range.start_bound() {
Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
Bound::Excluded(_) => unimplemented!(),
Bound::Unbounded => self.range.start,
};
let end = match range.end_bound() {
Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
Bound::Unbounded => self.range.end,
};
Text {
text: self.text.clone(),
runs: self.runs.clone(),
range: start..end,
}
}
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.runs.cursor::<usize, Point>();
cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_row = cursor.start().row + row;
let mut cursor = self.runs.cursor::<Point, usize>();
cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &());
let prefix_len = self.range.start.saturating_sub(*cursor.start());
let line_len =
cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left, &());
let suffix_len = cursor.start().saturating_sub(self.range.end);
line_len
.saturating_sub(prefix_len)
.saturating_sub(suffix_len) as u32
}
pub fn len(&self) -> usize {
self.range.end - self.range.start
}
pub fn lines(&self) -> Point {
self.abs_point_for_offset(self.range.end) - &self.abs_point_for_offset(self.range.start)
}
pub fn rightmost_point(&self) -> Point {
let lines = self.lines();
let mut candidates = ArrayVec::<[Point; 3]>::new();
candidates.push(lines);
if lines.row > 0 {
candidates.push(Point::new(0, self.line_len(0)));
if lines.row > 1 {
let mut cursor = self.runs.cursor::<usize, Point>();
cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_start_row = cursor.start().row;
let mut cursor = self.runs.cursor::<Point, usize>();
cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &());
let summary = cursor.summary::<TextSummary>(
&Point::new(absolute_start_row + lines.row, 0),
SeekBias::Left,
&(),
);
candidates.push(Point::new(1, 0) + &summary.rightmost_point);
}
}
candidates.into_iter().max_by_key(|p| p.column).unwrap()
}
pub fn point_for_offset(&self, offset: usize) -> Point {
self.abs_point_for_offset(self.range.start + offset)
- &self.abs_point_for_offset(self.range.start)
}
pub fn offset_for_point(&self, point: Point) -> usize {
let mut cursor = self.runs.cursor::<Point, TextSummary>();
let abs_point = self.abs_point_for_offset(self.range.start) + &point;
cursor.seek(&abs_point, SeekBias::Right, &());
let overshoot = abs_point - &cursor.start().lines;
let abs_offset = cursor.start().chars + overshoot.column as usize;
abs_offset - self.range.start
}
pub fn summary(&self) -> TextSummary {
TextSummary {
chars: self.range.end - self.range.start,
bytes: self.abs_byte_offset_for_offset(self.range.end)
- self.abs_byte_offset_for_offset(self.range.start),
lines: self.abs_point_for_offset(self.range.end)
- &self.abs_point_for_offset(self.range.start),
first_line_len: self.line_len(0),
rightmost_point: self.rightmost_point(),
}
}
fn abs_point_for_offset(&self, offset: usize) -> Point {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
cursor.seek(&offset, SeekBias::Right, &());
let overshoot = (offset - cursor.start().chars) as u32;
cursor.start().lines + &Point::new(0, overshoot)
}
fn abs_byte_offset_for_offset(&self, offset: usize) -> usize {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
cursor.seek(&offset, SeekBias::Right, &());
let overshoot = offset - cursor.start().chars;
cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use std::iter::FromIterator;
#[test]
fn test_basic() {
let text = Text::from(String::from("ab\ncd€\nfghij\nkl¢m"));
assert_eq!(text.len(), 17);
assert_eq!(text.as_str(), "ab\ncd€\nfghij\nkl¢m");
assert_eq!(text.lines(), Point::new(3, 4));
assert_eq!(text.line_len(0), 2);
assert_eq!(text.line_len(1), 3);
assert_eq!(text.line_len(2), 5);
assert_eq!(text.line_len(3), 4);
assert_eq!(text.rightmost_point(), Point::new(2, 5));
let b_to_g = text.slice(1..9);
assert_eq!(b_to_g.as_str(), "b\ncd€\nfg");
assert_eq!(b_to_g.len(), 8);
assert_eq!(b_to_g.lines(), Point::new(2, 2));
assert_eq!(b_to_g.line_len(0), 1);
assert_eq!(b_to_g.line_len(1), 3);
assert_eq!(b_to_g.line_len(2), 2);
assert_eq!(b_to_g.line_len(3), 0);
assert_eq!(b_to_g.rightmost_point(), Point::new(1, 3));
let d_to_i = text.slice(4..11);
assert_eq!(d_to_i.as_str(), "d€\nfghi");
assert_eq!(&d_to_i[1..5], "\nfg");
assert_eq!(d_to_i.len(), 7);
assert_eq!(d_to_i.lines(), Point::new(1, 4));
assert_eq!(d_to_i.line_len(0), 2);
assert_eq!(d_to_i.line_len(1), 4);
assert_eq!(d_to_i.line_len(2), 0);
assert_eq!(d_to_i.rightmost_point(), Point::new(1, 4));
let d_to_j = text.slice(4..=11);
assert_eq!(d_to_j.as_str(), "d€\nfghij");
assert_eq!(&d_to_j[1..], "\nfghij");
assert_eq!(d_to_j.len(), 8);
}
#[test]
fn test_random() {
use rand::prelude::*;
for seed in 0..100 {
println!("buffer::text seed: {}", seed);
let rng = &mut StdRng::seed_from_u64(seed);
let len = rng.gen_range(0..50);
let mut string = String::new();
for _ in 0..len {
if rng.gen_ratio(1, 5) {
string.push('\n');
} else {
string.push(rng.gen());
}
}
let text = Text::from(string.clone());
for _ in 0..10 {
let start = rng.gen_range(0..text.len() + 1);
let end = rng.gen_range(start..text.len() + 2);
let string_slice = string
.chars()
.skip(start)
.take(end - start)
.collect::<String>();
let expected_line_endpoints = string_slice
.split('\n')
.enumerate()
.map(|(row, line)| Point::new(row as u32, line.chars().count() as u32))
.collect::<Vec<_>>();
let text_slice = text.slice(start..end);
assert_eq!(text_slice.lines(), lines(&string_slice));
let mut rightmost_points: HashSet<Point> = HashSet::new();
for endpoint in &expected_line_endpoints {
if let Some(rightmost_point) = rightmost_points.iter().next().cloned() {
if endpoint.column > rightmost_point.column {
rightmost_points.clear();
}
if endpoint.column >= rightmost_point.column {
rightmost_points.insert(*endpoint);
}
} else {
rightmost_points.insert(*endpoint);
}
assert_eq!(text_slice.line_len(endpoint.row as u32), endpoint.column);
}
assert!(rightmost_points.contains(&text_slice.rightmost_point()));
for _ in 0..10 {
let offset = rng.gen_range(0..string_slice.chars().count() + 1);
let point = lines(&string_slice.chars().take(offset).collect::<String>());
assert_eq!(text_slice.point_for_offset(offset), point);
assert_eq!(text_slice.offset_for_point(point), offset);
if offset < string_slice.chars().count() {
assert_eq!(
&text_slice[offset..offset + 1],
String::from_iter(string_slice.chars().nth(offset)).as_str()
);
}
}
}
}
}
pub fn lines(s: &str) -> Point {
let mut row = 0;
let mut column = 0;
for ch in s.chars() {
if ch == '\n' {
row += 1;
column = 0;
} else {
column += 1;
}
}
Point::new(row, column)
}
}
+12 -4
View File
@@ -1,8 +1,9 @@
use super::{
buffer::{self, AnchorRangeExt},
Anchor, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
buffer::{AnchorRangeExt, TextSummary},
Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset,
};
use crate::{
editor::rope,
sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time,
};
@@ -607,7 +608,7 @@ pub struct Chars<'a> {
cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>,
offset: usize,
buffer: &'a Buffer,
buffer_chars: Option<Take<buffer::CharIter<'a>>>,
buffer_chars: Option<Take<rope::Chars<'a>>>,
}
impl<'a> Iterator for Chars<'a> {
@@ -669,8 +670,8 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::buffer::ToPoint;
use crate::test::sample_text;
use buffer::ToPoint;
#[gpui::test]
fn test_basic_folds(app: &mut gpui::MutableAppContext) {
@@ -916,6 +917,7 @@ mod tests {
assert_eq!(line_len, line.chars().count() as u32);
}
let rightmost_point = map.rightmost_point(app.as_ref());
let mut display_point = DisplayPoint::new(0, 0);
let mut display_offset = DisplayOffset(0);
for c in expected_text.chars() {
@@ -941,6 +943,12 @@ mod tests {
*display_point.column_mut() += 1;
}
display_offset.0 += 1;
if display_point.column() > rightmost_point.column() {
panic!(
"invalid rightmost point {:?}, found point {:?}",
rightmost_point, display_point
);
}
}
for _ in 0..5 {
+1 -1
View File
@@ -1,6 +1,6 @@
mod fold_map;
use super::{buffer, Anchor, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
use super::{buffer, Anchor, Buffer, Edit, Point, ToOffset, ToPoint};
use anyhow::Result;
pub use fold_map::BufferRows;
use fold_map::{FoldMap, FoldMapSnapshot};
+40
View File
@@ -101,6 +101,46 @@ impl<T: Item> SumTree<T> {
self.rightmost_leaf().0.items().last()
}
pub fn update_last(&mut self, f: impl FnOnce(&mut T), ctx: &<T::Summary as Summary>::Context) {
self.update_last_recursive(f, ctx);
}
fn update_last_recursive(
&mut self,
f: impl FnOnce(&mut T),
ctx: &<T::Summary as Summary>::Context,
) -> Option<T::Summary> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
summary,
child_summaries,
child_trees,
..
} => {
let last_summary = child_summaries.last_mut().unwrap();
let last_child = child_trees.last_mut().unwrap();
*last_summary = last_child.update_last_recursive(f, ctx).unwrap();
*summary = sum(child_summaries.iter(), ctx);
Some(summary.clone())
}
Node::Leaf {
summary,
items,
item_summaries,
} => {
if let Some((item, item_summary)) = items.last_mut().zip(item_summaries.last_mut())
{
(f)(item);
*item_summary = item.summary();
*summary = sum(item_summaries.iter(), ctx);
Some(summary.clone())
} else {
None
}
}
}
}
pub fn extent<'a, D: Dimension<'a, T::Summary>>(&'a self) -> D {
let mut extent = D::default();
match self.0.as_ref() {
+5 -10
View File
@@ -3,7 +3,7 @@ mod fuzzy;
mod ignore;
use crate::{
editor::{History, Snapshot as BufferSnapshot},
editor::{History, Rope},
sum_tree::{self, Cursor, Edit, SeekBias, SumTree},
};
use ::ignore::gitignore::Gitignore;
@@ -198,20 +198,15 @@ impl Worktree {
})
}
pub fn save<'a>(
&self,
path: &Path,
content: BufferSnapshot,
ctx: &AppContext,
) -> Task<Result<()>> {
pub fn save<'a>(&self, path: &Path, content: Rope, ctx: &AppContext) -> Task<Result<()>> {
let handles = self.handles.clone();
let path = path.to_path_buf();
let abs_path = self.absolutize(&path);
ctx.background_executor().spawn(async move {
let buffer_size = content.text_summary().bytes.min(10 * 1024);
let buffer_size = content.summary().bytes.min(10 * 1024);
let file = fs::File::create(&abs_path)?;
let mut writer = io::BufWriter::with_capacity(buffer_size, &file);
for chunk in content.fragments() {
for chunk in content.chunks() {
writer.write(chunk.as_bytes())?;
}
writer.flush()?;
@@ -459,7 +454,7 @@ impl FileHandle {
self.worktree.read(ctx).load_history(&self.path(), ctx)
}
pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
pub fn save<'a>(&self, content: Rope, ctx: &AppContext) -> Task<Result<()>> {
let worktree = self.worktree.read(ctx);
worktree.save(&self.path(), content, ctx)
}