Start work on WrapMap::chunks_at
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
8e9a5e072d
commit
f9e13f3429
6 changed files with 175 additions and 45 deletions
|
@ -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;
|
||||
|
|
|
@ -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(&());
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue