@@ -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(),]
);
}
}
@@ -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;
@@ -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);
}
}