From f9e13f34297e13a9f44f859f672e738a5b22693d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 19 Jul 2021 16:23:56 -0700 Subject: [PATCH] Start work on WrapMap::chunks_at Co-Authored-By: Nathan Sobo --- gpui/src/platform/mac/fonts.rs | 18 ++- zed/src/editor/buffer.rs | 20 ++-- zed/src/editor/display_map/fold_map.rs | 10 +- zed/src/editor/display_map/tab_map.rs | 10 +- zed/src/editor/display_map/wrap_map.rs | 160 ++++++++++++++++++++++--- zed/src/sum_tree/cursor.rs | 2 +- 6 files changed, 175 insertions(+), 45 deletions(-) diff --git a/gpui/src/platform/mac/fonts.rs b/gpui/src/platform/mac/fonts.rs index b4ca054470a4ee1ec44d9d2ee3ec057aa67e9ba1..2f29625232e8f3d6ecd2c6e4c305c09d125fea39 100644 --- a/gpui/src/platform/mac/fonts.rs +++ b/gpui/src/platform/mac/fonts.rs @@ -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(),] ); } } diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index b27cf5eb908bb52648984660002abfd10ae4d69a..746306909c0dfd7c9840567b4710d1df23df6db3 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -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; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 1d5f49810a766fb5ca06e5e12ea3bc33389d7fae..069d154291ba0781b205a8bc64d93a516eb9c047 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -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(&()); diff --git a/zed/src/editor/display_map/tab_map.rs b/zed/src/editor/display_map/tab_map.rs index 770a4df6de7917da8e5a20858b910e73f865e0fd..78c3031ab37d8d91af4b2cb3f0840ca9e48211d1 100644 --- a/zed/src/editor/display_map/tab_map.rs +++ b/zed/src/editor/display_map/tab_map.rs @@ -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); @@ -266,6 +266,14 @@ impl AddAssign for OutputPoint { } } +impl Add 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, diff --git a/zed/src/editor/display_map/wrap_map.rs b/zed/src/editor/display_map/wrap_map.rs index 89d8b6ed06c614457b0c62e5dd6625f52bf552fd..050a71b5b7328664fbf86afea9492ba851067f33 100644 --- a/zed/src/editor/display_map/wrap_map.rs +++ b/zed/src/editor/display_map/wrap_map.rs @@ -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 for OutputPoint { + fn add_assign(&mut self, rhs: Self) { + self.0 += &rhs.0; + } +} + +impl Sub for OutputPoint { + type Output = OutputPoint; + + fn sub(self, other: Self) -> Self::Output { + Self(self.0 - other.0) + } +} + #[derive(Clone)] pub struct Snapshot { transforms: SumTree, @@ -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 { + 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, ) { 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::(), + " 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::(); Buffer::new(0, text, cx) }); @@ -409,6 +534,7 @@ mod tests { prev_ix = ix; } } + dbg!(expected_text); } } diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index c4cc68e7785236daf6e900224541e6a127177b74..8a62cf4b489d59e0a33b474199d8daf18713e3ca 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -63,7 +63,7 @@ where &self.sum_dimension } - pub fn end(&self, cx: &::Context) -> U { + pub fn sum_end(&self, cx: &::Context) -> U { if let Some(item_summary) = self.item_summary() { let mut end = self.sum_start().clone(); end.add_summary(item_summary, cx);