Start work on WrapMap::chunks_at

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-07-19 16:23:56 -07:00
parent 8e9a5e072d
commit f9e13f3429
6 changed files with 175 additions and 45 deletions

View file

@ -316,6 +316,9 @@ impl FontSystemState {
width as f64,
) as usize;
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
if ix_converter.utf8_ix >= text.len() {
break;
}
break_indices.push(ix_converter.utf8_ix as usize);
}
break_indices
@ -485,22 +488,15 @@ mod tests {
let font_ids = fonts.load_family("Helvetica").unwrap();
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
let line = "one two three four five";
let line = "one two three four five\n";
let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
assert_eq!(
wrap_boundaries,
&["one two ".len(), "one two three ".len(), line.len()]
);
assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
let line = "aaa ααα ✋✋✋ 🎉🎉🎉";
let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
assert_eq!(
wrap_boundaries,
&[
"aaa ααα ".len(),
"aaa ααα ✋✋✋ ".len(),
"aaa ααα ✋✋✋ 🎉🎉🎉".len(),
]
&["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
);
}
}

View file

@ -1416,7 +1416,7 @@ impl Buffer {
let mut fragment_start = old_fragments.sum_start().offset();
for range in ranges {
let fragment_end = old_fragments.end(&cx).offset();
let fragment_end = old_fragments.sum_end(&cx).offset();
// If the current fragment ends before this range, then jump ahead to the first fragment
// that extends past the start of this range, reusing any intervening fragments.
@ -1441,7 +1441,7 @@ impl Buffer {
}
// If we are at the end of a non-concurrent fragment, advance to the next one.
let fragment_end = old_fragments.end(&cx).offset();
let fragment_end = old_fragments.sum_end(&cx).offset();
if fragment_end == range.start && fragment_end > fragment_start {
let mut fragment = old_fragments.item().unwrap().clone();
fragment.len = fragment_end - fragment_start;
@ -1495,7 +1495,7 @@ impl Buffer {
// portions as deleted.
while fragment_start < range.end {
let fragment = old_fragments.item().unwrap();
let fragment_end = old_fragments.end(&cx).offset();
let fragment_end = old_fragments.sum_end(&cx).offset();
let mut intersection = fragment.clone();
let intersection_end = cmp::min(range.end, fragment_end);
if fragment.was_visible(version, &self.undo_map) {
@ -1517,7 +1517,7 @@ impl Buffer {
// If the current fragment has been partially consumed, then consume the rest of it
// and advance to the next fragment before slicing.
if fragment_start > old_fragments.sum_start().offset() {
let fragment_end = old_fragments.end(&cx).offset();
let fragment_end = old_fragments.sum_end(&cx).offset();
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
suffix.len = fragment_end - fragment_start;
@ -1644,7 +1644,7 @@ impl Buffer {
new_ropes.push_tree(new_fragments.summary().text);
for range in &undo.ranges {
let mut end_offset = old_fragments.end(&cx).offset();
let mut end_offset = old_fragments.sum_end(&cx).offset();
if end_offset < range.start {
let preceding_fragments =
@ -1668,7 +1668,7 @@ impl Buffer {
new_fragments.push(fragment, &None);
old_fragments.next(&cx);
if end_offset == old_fragments.end(&cx).offset() {
if end_offset == old_fragments.sum_end(&cx).offset() {
let unseen_fragments = old_fragments.slice(
&VersionedOffset::Offset(end_offset),
Bias::Right,
@ -1677,7 +1677,7 @@ impl Buffer {
new_ropes.push_tree(unseen_fragments.summary().text);
new_fragments.push_tree(unseen_fragments, &None);
}
end_offset = old_fragments.end(&cx).offset();
end_offset = old_fragments.sum_end(&cx).offset();
} else {
break;
}
@ -1757,7 +1757,7 @@ impl Buffer {
let mut fragment_start = old_fragments.sum_start().visible;
for range in ranges {
let fragment_end = old_fragments.end(&None).visible;
let fragment_end = old_fragments.sum_end(&None).visible;
// If the current fragment ends before this range, then jump ahead to the first fragment
// that extends past the start of this range, reusing any intervening fragments.
@ -1810,7 +1810,7 @@ impl Buffer {
// portions as deleted.
while fragment_start < range.end {
let fragment = old_fragments.item().unwrap();
let fragment_end = old_fragments.end(&None).visible;
let fragment_end = old_fragments.sum_end(&None).visible;
let mut intersection = fragment.clone();
let intersection_end = cmp::min(range.end, fragment_end);
if fragment.visible {
@ -1835,7 +1835,7 @@ impl Buffer {
// If the current fragment has been partially consumed, then consume the rest of it
// and advance to the next fragment before slicing.
if fragment_start > old_fragments.sum_start().visible {
let fragment_end = old_fragments.end(&None).visible;
let fragment_end = old_fragments.sum_end(&None).visible;
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
suffix.len = fragment_end - fragment_start;

View file

@ -634,7 +634,7 @@ impl Snapshot {
let overshoot = point - cursor.seek_start();
OutputPoint(cmp::min(
cursor.sum_start().0 + overshoot,
cursor.end(&()).0,
cursor.sum_end(&()).0,
))
}
@ -936,7 +936,7 @@ impl<'a> Iterator for Chunks<'a> {
self.input_offset += transform.summary.input.bytes;
self.input_chunks.seek(self.input_offset);
while self.input_offset >= self.transform_cursor.end(&())
while self.input_offset >= self.transform_cursor.sum_end(&())
&& self.transform_cursor.item().is_some()
{
self.transform_cursor.next(&());
@ -951,7 +951,7 @@ impl<'a> Iterator for Chunks<'a> {
chunk = &chunk[offset_in_chunk..];
// Truncate the chunk so that it ends at the next fold.
let region_end = self.transform_cursor.end(&()) - self.input_offset;
let region_end = self.transform_cursor.sum_end(&()) - self.input_offset;
if chunk.len() >= region_end {
chunk = &chunk[0..region_end];
self.transform_cursor.next(&());
@ -991,7 +991,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
self.input_offset += transform.summary.input.bytes;
self.input_chunks.seek(self.input_offset);
while self.input_offset >= self.transform_cursor.end(&())
while self.input_offset >= self.transform_cursor.sum_end(&())
&& self.transform_cursor.item().is_some()
{
self.transform_cursor.next(&());
@ -1015,7 +1015,7 @@ impl<'a> Iterator for HighlightedChunks<'a> {
chunk = &chunk[offset_in_chunk..];
// Truncate the chunk so that it ends at the next fold.
let region_end = self.transform_cursor.end(&()) - self.input_offset;
let region_end = self.transform_cursor.sum_end(&()) - self.input_offset;
if chunk.len() >= region_end {
chunk = &chunk[0..region_end];
self.transform_cursor.next(&());

View file

@ -7,7 +7,7 @@ use super::fold_map::{
use crate::{editor::rope, settings::StyleId, util::Bias};
use std::{
mem,
ops::{AddAssign, Range},
ops::{Add, AddAssign, Range},
};
pub struct TabMap(Mutex<Snapshot>);
@ -266,6 +266,14 @@ impl AddAssign<Self> for OutputPoint {
}
}
impl Add<Self> for OutputPoint {
type Output = OutputPoint;
fn add(self, other: Self) -> Self::Output {
Self(self.0 + other.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Edit {
pub old_lines: Range<OutputPoint>,

View file

@ -1,16 +1,19 @@
use super::tab_map::{
Edit as InputEdit, OutputPoint as InputPoint, Snapshot as InputSnapshot, TextSummary,
self, Edit as InputEdit, OutputPoint as InputPoint, Snapshot as InputSnapshot, TextSummary,
};
use crate::{
editor::Point,
sum_tree::{self, SumTree},
sum_tree::{self, Cursor, SumTree},
util::Bias,
};
use gpui::{font_cache::FamilyId, AppContext, FontCache, FontSystem, Task};
use parking_lot::Mutex;
use postage::{prelude::Sink, watch};
use smol::channel;
use std::{ops::Range, sync::Arc};
use std::{
ops::{AddAssign, Range, Sub},
sync::Arc,
};
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct OutputPoint(super::Point);
@ -41,6 +44,20 @@ impl OutputPoint {
}
}
impl AddAssign<Self> for OutputPoint {
fn add_assign(&mut self, rhs: Self) {
self.0 += &rhs.0;
}
}
impl Sub<Self> for OutputPoint {
type Output = OutputPoint;
fn sub(self, other: Self) -> Self::Output {
Self(self.0 - other.0)
}
}
#[derive(Clone)]
pub struct Snapshot {
transforms: SumTree<Transform>,
@ -65,6 +82,65 @@ impl Snapshot {
input,
}
}
pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
let mut transforms = self.transforms.cursor();
transforms.seek(&point, Bias::Right, &());
let input_position =
*transforms.sum_start() + InputPoint((point - *transforms.seek_start()).0);
let input_chunks = self.input.chunks_at(input_position);
Chunks {
input_chunks,
transforms,
input_position,
input_chunk: "",
}
}
}
pub struct Chunks<'a> {
input_chunks: tab_map::Chunks<'a>,
input_chunk: &'a str,
input_position: InputPoint,
transforms: Cursor<'a, Transform, OutputPoint, InputPoint>,
}
impl<'a> Iterator for Chunks<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let transform = self.transforms.item()?;
if let Some(display_text) = transform.display_text {
self.transforms.next(&());
return Some(display_text);
}
if self.input_chunk.is_empty() {
self.input_chunk = self.input_chunks.next().unwrap();
}
let mut input_len = 0;
let transform_end = self.transforms.sum_end(&());
for c in self.input_chunk.chars() {
let char_len = c.len_utf8();
input_len += char_len;
if c == '\n' {
*self.input_position.row_mut() += 1;
*self.input_position.column_mut() = 0;
} else {
*self.input_position.column_mut() += char_len as u32;
}
if self.input_position >= transform_end {
self.transforms.next(&());
break;
}
}
let (prefix, suffix) = self.input_chunk.split_at(input_len);
self.input_chunk = suffix;
Some(prefix)
}
}
struct State {
@ -149,7 +225,7 @@ impl BackgroundWrapper {
mut snapshots_tx: watch::Sender<Snapshot>,
) {
let edit = InputEdit {
old_lines: Default::default()..Default::default(),
old_lines: Default::default()..snapshot.max_point(),
new_lines: Default::default()..snapshot.max_point(),
};
self.sync(snapshot, vec![edit]);
@ -214,6 +290,8 @@ impl BackgroundWrapper {
'outer: for chunk in new_snapshot.chunks_at(InputPoint::new(row, 0)) {
for (ix, line_chunk) in chunk.split('\n').enumerate() {
if ix > 0 {
line.push('\n');
let mut prev_boundary_ix = 0;
for boundary_ix in self
.font_system
@ -226,6 +304,15 @@ impl BackgroundWrapper {
prev_boundary_ix = boundary_ix;
}
if prev_boundary_ix < line.len() {
new_transforms.push(
Transform::isomorphic(TextSummary::from(
&line[prev_boundary_ix..],
)),
&(),
);
}
line.clear();
row += 1;
if row == edit.new_rows.end {
@ -238,14 +325,17 @@ impl BackgroundWrapper {
}
old_cursor.seek_forward(&InputPoint::new(edit.old_rows.end, 0), Bias::Right, &());
if old_cursor.seek_end(&()).row() > edit.old_rows.end {
new_transforms.push(
Transform::isomorphic(self.snapshot.input.text_summary_for_rows(
edit.old_rows.end..old_cursor.seek_end(&()).row(),
)),
&(),
);
}
if let Some(next_edit) = edits.peek() {
if next_edit.old_rows.start > old_cursor.seek_end(&()).row() {
new_transforms.push(
Transform::isomorphic(self.snapshot.input.text_summary_for_rows(
edit.old_rows.end..old_cursor.seek_end(&()).row(),
)),
&(),
);
old_cursor.next(&());
new_transforms.push_tree(
old_cursor.slice(
@ -257,12 +347,6 @@ impl BackgroundWrapper {
);
}
} else {
new_transforms.push(
Transform::isomorphic(self.snapshot.input.text_summary_for_rows(
edit.old_rows.end..old_cursor.seek_end(&()).row(),
)),
&(),
);
old_cursor.next(&());
new_transforms.push_tree(old_cursor.suffix(&()), &());
}
@ -282,6 +366,10 @@ struct Transform {
impl Transform {
fn isomorphic(summary: TextSummary) -> Self {
if summary.lines.is_zero() {
panic!("wtf");
}
Self {
summary: TransformSummary {
input: summary.clone(),
@ -337,6 +425,12 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InputPoint {
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputPoint {
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
*self += OutputPoint(summary.output.lines);
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -347,9 +441,40 @@ mod tests {
},
util::RandomCharIter,
};
use futures::StreamExt;
use rand::prelude::*;
use std::env;
#[gpui::test]
async fn test_simple_wraps(mut cx: gpui::TestAppContext) {
let text = "one two three four five six\n";
let font_cache = cx.font_cache().clone();
let config = Config {
wrap_width: 64.,
font_family: font_cache.load_family(&["Helvetica"]).unwrap(),
font_size: 14.0,
};
let buffer = cx.add_model(|cx| Buffer::new(0, text.to_string(), cx));
let mut wrap_map = cx.read(|cx| {
let fold_map = FoldMap::new(buffer.clone(), cx);
let (folds_snapshot, edits) = fold_map.read(cx);
let tab_map = TabMap::new(folds_snapshot.clone(), 4);
let (tabs_snapshot, _) = tab_map.sync(folds_snapshot, edits);
WrapMap::new(tabs_snapshot, config, cx)
});
wrap_map.background_snapshots.next().await;
let snapshot = wrap_map.background_snapshots.next().await.unwrap();
assert_eq!(
snapshot
.chunks_at(OutputPoint(Point::new(0, 3)))
.collect::<String>(),
" two \nthree four \nfive six\n"
);
}
#[gpui::test]
fn test_random_wraps(cx: &mut gpui::MutableAppContext) {
let iterations = env::var("ITERATIONS")
@ -370,7 +495,7 @@ mod tests {
let mut rng = StdRng::seed_from_u64(seed);
let buffer = cx.add_model(|cx| {
let len = rng.gen_range(0..10);
let len = rng.gen_range(0..32);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
Buffer::new(0, text, cx)
});
@ -409,6 +534,7 @@ mod tests {
prev_ix = ix;
}
}
dbg!(expected_text);
}
}

View file

@ -63,7 +63,7 @@ where
&self.sum_dimension
}
pub fn end(&self, cx: &<T::Summary as Summary>::Context) -> U {
pub fn sum_end(&self, cx: &<T::Summary as Summary>::Context) -> U {
if let Some(item_summary) = self.item_summary() {
let mut end = self.sum_start().clone();
end.add_summary(item_summary, cx);