From 9970e5f60c27da445460741e41c131f9a21df343 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 15:56:15 +0100 Subject: [PATCH] Start on randomized test and add `SuggestionMapSnapshot::chunks` --- .../editor/src/display_map/suggestion_map.rs | 228 +++++++++++++++++- 1 file changed, 220 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 6e438234be..8b514cc68e 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -1,11 +1,15 @@ -use std::ops::{Add, AddAssign, Sub}; - -use crate::{ToOffset, ToPoint}; - -use super::fold_map::{FoldEdit, FoldOffset, FoldSnapshot}; +use super::{ + fold_map::{FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}, + TextHighlights, +}; +use crate::ToPoint; use gpui::fonts::HighlightStyle; -use language::{Bias, Edit, Patch, Rope}; +use language::{Bias, Chunk, Edit, Patch, Point, Rope}; use parking_lot::Mutex; +use std::{ + cmp, + ops::{Add, AddAssign, Range, Sub}, +}; pub type SuggestionEdit = Edit; @@ -34,15 +38,26 @@ impl AddAssign for SuggestionOffset { } } +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct SuggestionPoint(pub Point); + #[derive(Clone)] pub struct Suggestion { position: T, text: Rope, + highlight_style: HighlightStyle, } pub struct SuggestionMap(Mutex); impl SuggestionMap { + pub fn new(fold_snapshot: FoldSnapshot) -> Self { + Self(Mutex::new(SuggestionSnapshot { + fold_snapshot, + suggestion: None, + })) + } + pub fn replace( &mut self, new_suggestion: Option>, @@ -61,6 +76,7 @@ impl SuggestionMap { Suggestion { position: fold_offset, text: new_suggestion.text, + highlight_style: new_suggestion.highlight_style, } }); @@ -128,7 +144,7 @@ impl SuggestionMap { ..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len), }); } - snapshot.folds_snapshot = fold_snapshot; + snapshot.fold_snapshot = fold_snapshot; (snapshot.clone(), suggestion_edits) } @@ -136,6 +152,202 @@ impl SuggestionMap { #[derive(Clone)] pub struct SuggestionSnapshot { - folds_snapshot: FoldSnapshot, + fold_snapshot: FoldSnapshot, suggestion: Option>, } + +impl SuggestionSnapshot { + pub fn max_point(&self) -> SuggestionPoint { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_point = suggestion.position.to_point(&self.fold_snapshot); + let mut max_point = suggestion_point.0; + max_point += suggestion.text.max_point(); + max_point += self.fold_snapshot.max_point().0 - suggestion_point.0; + SuggestionPoint(max_point) + } else { + SuggestionPoint(self.fold_snapshot.max_point().0) + } + } + + pub fn len(&self) -> SuggestionOffset { + if let Some(suggestion) = self.suggestion.as_ref() { + let mut len = suggestion.position.0; + len += suggestion.text.len(); + len += self.fold_snapshot.len().0 - suggestion.position.0; + SuggestionOffset(len) + } else { + SuggestionOffset(self.fold_snapshot.len().0) + } + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> Chunks<'a> { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_range = + suggestion.position.0..suggestion.position.0 + suggestion.text.len(); + + let prefix_chunks = if range.start.0 < suggestion_range.start { + Some(self.fold_snapshot.chunks( + FoldOffset(range.start.0) + ..cmp::min(FoldOffset(suggestion_range.start), FoldOffset(range.end.0)), + language_aware, + text_highlights, + )) + } else { + None + }; + + let clipped_suggestion_range = cmp::max(range.start.0, suggestion_range.start) + ..cmp::min(range.end.0, suggestion_range.end); + let suggestion_chunks = if clipped_suggestion_range.start < clipped_suggestion_range.end + { + let start = clipped_suggestion_range.start - suggestion_range.start; + let end = clipped_suggestion_range.end - suggestion_range.start; + Some(suggestion.text.chunks_in_range(start..end)) + } else { + None + }; + + let suffix_chunks = if range.end.0 > suggestion_range.end { + let start = cmp::max(suggestion_range.end, range.start.0) - suggestion_range.len(); + let end = range.end.0 - suggestion_range.len(); + Some(self.fold_snapshot.chunks( + FoldOffset(start)..FoldOffset(end), + language_aware, + text_highlights, + )) + } else { + None + }; + + Chunks { + prefix_chunks, + suggestion_chunks, + suffix_chunks, + highlight_style: suggestion.highlight_style, + } + } else { + Chunks { + prefix_chunks: Some(self.fold_snapshot.chunks( + FoldOffset(range.start.0)..FoldOffset(range.end.0), + language_aware, + text_highlights, + )), + suggestion_chunks: None, + suffix_chunks: None, + highlight_style: Default::default(), + } + } + } + + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks(Default::default()..self.len(), false, None) + .map(|chunk| chunk.text) + .collect() + } +} + +pub struct Chunks<'a> { + prefix_chunks: Option>, + suggestion_chunks: Option>, + suffix_chunks: Option>, + highlight_style: HighlightStyle, +} + +impl<'a> Iterator for Chunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if let Some(chunks) = self.prefix_chunks.as_mut() { + if let Some(chunk) = chunks.next() { + return Some(chunk); + } else { + self.prefix_chunks = None; + } + } + + if let Some(chunks) = self.suggestion_chunks.as_mut() { + if let Some(chunk) = chunks.next() { + return Some(Chunk { + text: chunk, + syntax_highlight_id: None, + highlight_style: Some(self.highlight_style), + diagnostic_severity: None, + is_unnecessary: false, + }); + } else { + self.suggestion_chunks = None; + } + } + + if let Some(chunks) = self.suffix_chunks.as_mut() { + if let Some(chunk) = chunks.next() { + return Some(chunk); + } else { + self.suffix_chunks = None; + } + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{display_map::fold_map::FoldMap, MultiBuffer}; + use gpui::MutableAppContext; + use rand::{prelude::StdRng, Rng}; + + #[gpui::test(iterations = 100)] + fn test_random_suggestions(cx: &mut MutableAppContext, mut rng: StdRng) { + cx.set_global(Settings::test(cx)); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let len = rng.gen_range(0..30); + let buffer = if rng.gen() { + let text = util::RandomCharIter::new(&mut rng) + .take(len) + .collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let buffer_snapshot = buffer.read(cx).snapshot(cx); + log::info!("Buffer text: {:?}", buffer_snapshot.text()); + + let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone()); + let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); + let suggestion_map = SuggestionMap::new(fold_snapshot.clone()); + + for _ in 0..operations { + let mut buffer_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=59 => { + for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { + suggestion_map.sync(fold_snapshot, fold_edits); + } + } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), + }; + + log::info!("buffer text: {:?}", buffer_snapshot.text()); + log::info!("folds text: {:?}", buffer_snapshot.text()); + } + } +}