From 94a9e28e35aaf35a1539ae25a5ec1a4d29b79632 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 13:22:14 +0100 Subject: [PATCH 01/20] Start on `SuggestionMap` --- crates/editor/src/display_map.rs | 1 + .../editor/src/display_map/suggestion_map.rs | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 crates/editor/src/display_map/suggestion_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f8154e6642947ebbe54db2b9fcbfb5973709db19..777fa2e9f51566487149c217f7860a646a7639e5 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,5 +1,6 @@ mod block_map; mod fold_map; +mod suggestion_map; mod tab_map; mod wrap_map; diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..b02a5e8866110a30fbffa11fb2038e167e872ea9 --- /dev/null +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -0,0 +1,71 @@ +use super::fold_map::{FoldEdit, FoldOffset, FoldSnapshot}; +use gpui::fonts::HighlightStyle; +use language::{Edit, Rope}; +use parking_lot::Mutex; + +pub type SuggestionEdit = Edit; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct SuggestionOffset(pub usize); + +#[derive(Clone)] +pub struct Suggestion { + position: FoldOffset, + text: Rope, + highlight_style: HighlightStyle, +} + +pub struct SuggestionMap(Mutex); + +impl SuggestionMap { + pub fn sync( + &self, + fold_snapshot: FoldSnapshot, + fold_edits: Vec, + ) -> (SuggestionSnapshot, Vec) { + let mut snapshot = self.0.lock(); + let mut suggestion_edits = Vec::new(); + + let mut suggestion_old_len = 0; + let mut suggestion_new_len = 0; + for fold_edit in fold_edits { + let start = fold_edit.new.start; + let end = FoldOffset(start.0 + fold_edit.old_len().0); + if let Some(suggestion) = snapshot.suggestion.as_mut() { + if end < suggestion.position { + suggestion.position.0 += fold_edit.new_len().0; + suggestion.position.0 -= fold_edit.old_len().0; + } else if start > suggestion.position { + suggestion_old_len = suggestion.text.len(); + suggestion_new_len = suggestion_old_len; + } else { + suggestion_old_len = suggestion.text.len(); + snapshot.suggestion.take(); + suggestion_edits.push(SuggestionEdit { + old: SuggestionOffset(fold_edit.old.start.0) + ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), + new: SuggestionOffset(fold_edit.new.start.0) + ..SuggestionOffset(fold_edit.new.end.0), + }); + continue; + } + } + + suggestion_edits.push(SuggestionEdit { + old: SuggestionOffset(fold_edit.old.start.0 + suggestion_old_len) + ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), + new: SuggestionOffset(fold_edit.new.start.0 + suggestion_new_len) + ..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len), + }); + } + snapshot.folds_snapshot = fold_snapshot; + + (snapshot.clone(), suggestion_edits) + } +} + +#[derive(Clone)] +pub struct SuggestionSnapshot { + folds_snapshot: FoldSnapshot, + suggestion: Option, +} From 83051f1e860b0c8e1749f5320821226865d5d2e0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 13:50:14 +0100 Subject: [PATCH 02/20] Add `SuggestionMap::replace` --- .../editor/src/display_map/suggestion_map.rs | 73 +++++++++++++++++-- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index b02a5e8866110a30fbffa11fb2038e167e872ea9..95eca9f9e97af1291bdb6409f3d3b2f2cd0155ae 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -1,6 +1,10 @@ +use std::ops::{Add, AddAssign, Sub}; + +use crate::{ToOffset, ToPoint}; + use super::fold_map::{FoldEdit, FoldOffset, FoldSnapshot}; use gpui::fonts::HighlightStyle; -use language::{Edit, Rope}; +use language::{Bias, Edit, Patch, Rope}; use parking_lot::Mutex; pub type SuggestionEdit = Edit; @@ -8,16 +12,71 @@ pub type SuggestionEdit = Edit; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct SuggestionOffset(pub usize); +impl Add for SuggestionOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for SuggestionOffset { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl AddAssign for SuggestionOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + #[derive(Clone)] pub struct Suggestion { - position: FoldOffset, + offset: FoldOffset, text: Rope, - highlight_style: HighlightStyle, } pub struct SuggestionMap(Mutex); impl SuggestionMap { + pub fn replace( + &mut self, + position: P, + text: T, + fold_snapshot: FoldSnapshot, + fold_edits: Vec, + ) -> (SuggestionSnapshot, Vec) + where + P: ToPoint, + T: Into, + { + let buffer_point = position.to_point(fold_snapshot.buffer_snapshot()); + let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); + let fold_offset = fold_point.to_offset(&fold_snapshot); + let new_suggestion = Suggestion { + offset: fold_offset, + text: text.into(), + }; + + let (_, edits) = self.sync(fold_snapshot, fold_edits); + let mut snapshot = self.0.lock(); + let old = if let Some(suggestion) = snapshot.suggestion.take() { + SuggestionOffset(suggestion.offset.0) + ..SuggestionOffset(suggestion.offset.0 + suggestion.text.len()) + } else { + SuggestionOffset(new_suggestion.offset.0)..SuggestionOffset(new_suggestion.offset.0) + }; + let new = SuggestionOffset(new_suggestion.offset.0) + ..SuggestionOffset(new_suggestion.offset.0 + new_suggestion.text.len()); + let patch = Patch::new(edits).compose([SuggestionEdit { old, new }]); + snapshot.suggestion = Some(new_suggestion); + (snapshot.clone(), patch.into_inner()) + } + pub fn sync( &self, fold_snapshot: FoldSnapshot, @@ -32,10 +91,10 @@ impl SuggestionMap { let start = fold_edit.new.start; let end = FoldOffset(start.0 + fold_edit.old_len().0); if let Some(suggestion) = snapshot.suggestion.as_mut() { - if end < suggestion.position { - suggestion.position.0 += fold_edit.new_len().0; - suggestion.position.0 -= fold_edit.old_len().0; - } else if start > suggestion.position { + if end < suggestion.offset { + suggestion.offset.0 += fold_edit.new_len().0; + suggestion.offset.0 -= fold_edit.old_len().0; + } else if start > suggestion.offset { suggestion_old_len = suggestion.text.len(); suggestion_new_len = suggestion_old_len; } else { From fb48854e5a2dede8202fd8e696d9efa2cc19dc42 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 14:00:14 +0100 Subject: [PATCH 03/20] Simplify signature of `SuggestionMap::replace` --- .../editor/src/display_map/suggestion_map.rs | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 95eca9f9e97af1291bdb6409f3d3b2f2cd0155ae..6e438234be4ffbeef588db387c5dda6b8af22690 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -35,45 +35,56 @@ impl AddAssign for SuggestionOffset { } #[derive(Clone)] -pub struct Suggestion { - offset: FoldOffset, +pub struct Suggestion { + position: T, text: Rope, } pub struct SuggestionMap(Mutex); impl SuggestionMap { - pub fn replace( + pub fn replace( &mut self, - position: P, - text: T, + new_suggestion: Option>, fold_snapshot: FoldSnapshot, fold_edits: Vec, ) -> (SuggestionSnapshot, Vec) where - P: ToPoint, - T: Into, + T: ToPoint, { - let buffer_point = position.to_point(fold_snapshot.buffer_snapshot()); - let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&fold_snapshot); - let new_suggestion = Suggestion { - offset: fold_offset, - text: text.into(), - }; + let new_suggestion = new_suggestion.map(|new_suggestion| { + let buffer_point = new_suggestion + .position + .to_point(fold_snapshot.buffer_snapshot()); + let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); + let fold_offset = fold_point.to_offset(&fold_snapshot); + Suggestion { + position: fold_offset, + text: new_suggestion.text, + } + }); let (_, edits) = self.sync(fold_snapshot, fold_edits); let mut snapshot = self.0.lock(); + let old = if let Some(suggestion) = snapshot.suggestion.take() { - SuggestionOffset(suggestion.offset.0) - ..SuggestionOffset(suggestion.offset.0 + suggestion.text.len()) + SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()) + } else if let Some(new_suggestion) = new_suggestion.as_ref() { + SuggestionOffset(new_suggestion.position.0)..SuggestionOffset(new_suggestion.position.0) } else { - SuggestionOffset(new_suggestion.offset.0)..SuggestionOffset(new_suggestion.offset.0) + return (snapshot.clone(), edits); }; - let new = SuggestionOffset(new_suggestion.offset.0) - ..SuggestionOffset(new_suggestion.offset.0 + new_suggestion.text.len()); + + let new = if let Some(suggestion) = new_suggestion.as_ref() { + SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()) + } else { + old.start..old.start + }; + let patch = Patch::new(edits).compose([SuggestionEdit { old, new }]); - snapshot.suggestion = Some(new_suggestion); + snapshot.suggestion = new_suggestion; (snapshot.clone(), patch.into_inner()) } @@ -91,10 +102,10 @@ impl SuggestionMap { let start = fold_edit.new.start; let end = FoldOffset(start.0 + fold_edit.old_len().0); if let Some(suggestion) = snapshot.suggestion.as_mut() { - if end < suggestion.offset { - suggestion.offset.0 += fold_edit.new_len().0; - suggestion.offset.0 -= fold_edit.old_len().0; - } else if start > suggestion.offset { + if end < suggestion.position { + suggestion.position.0 += fold_edit.new_len().0; + suggestion.position.0 -= fold_edit.old_len().0; + } else if start > suggestion.position { suggestion_old_len = suggestion.text.len(); suggestion_new_len = suggestion_old_len; } else { @@ -126,5 +137,5 @@ impl SuggestionMap { #[derive(Clone)] pub struct SuggestionSnapshot { folds_snapshot: FoldSnapshot, - suggestion: Option, + suggestion: Option>, } From 9970e5f60c27da445460741e41c131f9a21df343 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 15:56:15 +0100 Subject: [PATCH 04/20] 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 6e438234be4ffbeef588db387c5dda6b8af22690..8b514cc68e53ab1a49d92d5445156c64ed20dcc3 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()); + } + } +} From 4d6726ef3921c272ca41f1bf7b0fd76cb7abb274 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 17:25:36 +0100 Subject: [PATCH 05/20] WIP: Flesh out more of the suggestions randomized test --- .../editor/src/display_map/suggestion_map.rs | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 8b514cc68e53ab1a49d92d5445156c64ed20dcc3..b9a497fe7408c4b3bb70c890c9d217761d9857c1 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -51,15 +51,16 @@ pub struct Suggestion { pub struct SuggestionMap(Mutex); impl SuggestionMap { - pub fn new(fold_snapshot: FoldSnapshot) -> Self { - Self(Mutex::new(SuggestionSnapshot { + pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) { + let snapshot = SuggestionSnapshot { fold_snapshot, suggestion: None, - })) + }; + (Self(Mutex::new(snapshot.clone())), snapshot) } pub fn replace( - &mut self, + &self, new_suggestion: Option>, fold_snapshot: FoldSnapshot, fold_edits: Vec, @@ -303,6 +304,8 @@ mod tests { use crate::{display_map::fold_map::FoldMap, MultiBuffer}; use gpui::MutableAppContext; use rand::{prelude::StdRng, Rng}; + use settings::Settings; + use std::env; #[gpui::test(iterations = 100)] fn test_random_suggestions(cx: &mut MutableAppContext, mut rng: StdRng) { @@ -320,19 +323,44 @@ mod tests { } else { MultiBuffer::build_random(&mut rng, cx) }; - let buffer_snapshot = buffer.read(cx).snapshot(cx); - log::info!("Buffer text: {:?}", buffer_snapshot.text()); + let mut 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()); + let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + let mut suggestion_edits = Vec::new(); 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); + 0..=29 => { + let new_suggestion = if rng.gen_bool(0.3) { + None + } else { + let index = rng.gen_range(0..=buffer_snapshot.len()); + let len = rng.gen_range(0..30); + Some(Suggestion { + position: index, + text: util::RandomCharIter::new(&mut rng) + .take(len) + .collect::() + .as_str() + .into(), + highlight_style: Default::default(), + }) + }; + let (new_suggestion_snapshot, edits) = + suggestion_map.replace(new_suggestion, fold_snapshot, Default::default()); + suggestion_snapshot = new_suggestion_snapshot; + suggestion_edits.push(edits); + } + 30..=59 => { + for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { + fold_snapshot = new_fold_snapshot; + let (new_suggestion_snapshot, edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_snapshot = new_suggestion_snapshot; + suggestion_edits.push(edits); } } _ => buffer.update(cx, |buffer, cx| { @@ -346,8 +374,17 @@ mod tests { }), }; + let (new_fold_snapshot, fold_edits) = + fold_map.read(buffer_snapshot.clone(), buffer_edits); + fold_snapshot = new_fold_snapshot; + let (new_suggestion_snapshot, edits) = + suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_snapshot = new_suggestion_snapshot; + suggestion_edits.push(edits); + log::info!("buffer text: {:?}", buffer_snapshot.text()); - log::info!("folds text: {:?}", buffer_snapshot.text()); + log::info!("folds text: {:?}", fold_snapshot.text()); + log::info!("suggestions text: {:?}", suggestion_snapshot.text()); } } } From f44549eb299e9a84e702089e81b9b20c7873b10c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 19:17:23 +0100 Subject: [PATCH 06/20] Enhance randomized test to verify `SuggestionMap::{chunks,sync}` Co-Authored-By: Max Brunsfeld --- .../editor/src/display_map/suggestion_map.rs | 89 +++++++++++++------ crates/rope/src/rope.rs | 18 +++- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index b9a497fe7408c4b3bb70c890c9d217761d9857c1..f2b7433d355d138bea68295e041f6977772fe6d8 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -41,7 +41,7 @@ impl AddAssign for SuggestionOffset { #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct SuggestionPoint(pub Point); -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Suggestion { position: T, text: Rope, @@ -84,23 +84,25 @@ impl SuggestionMap { let (_, edits) = self.sync(fold_snapshot, fold_edits); let mut snapshot = self.0.lock(); - let old = if let Some(suggestion) = snapshot.suggestion.take() { - SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()) - } else if let Some(new_suggestion) = new_suggestion.as_ref() { - SuggestionOffset(new_suggestion.position.0)..SuggestionOffset(new_suggestion.position.0) - } else { - return (snapshot.clone(), edits); - }; + let mut patch = Patch::new(edits); + if let Some(suggestion) = snapshot.suggestion.take() { + patch = patch.compose([SuggestionEdit { + old: SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), + new: SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0), + }]); + } - let new = if let Some(suggestion) = new_suggestion.as_ref() { - SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()) - } else { - old.start..old.start - }; + if let Some(suggestion) = new_suggestion.as_ref() { + patch = patch.compose([SuggestionEdit { + old: SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0), + new: SuggestionOffset(suggestion.position.0) + ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), + }]); + } - let patch = Patch::new(edits).compose([SuggestionEdit { old, new }]); snapshot.suggestion = new_suggestion; (snapshot.clone(), patch.into_inner()) } @@ -328,9 +330,11 @@ mod tests { let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - let mut suggestion_edits = Vec::new(); for _ in 0..operations { + let mut suggestion_edits = Patch::default(); + + let mut prev_suggestion_text = suggestion_snapshot.text(); let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=29 => { @@ -349,18 +353,17 @@ mod tests { highlight_style: Default::default(), }) }; - let (new_suggestion_snapshot, edits) = + + log::info!("replacing suggestion with {:?}", new_suggestion); + let (_, edits) = suggestion_map.replace(new_suggestion, fold_snapshot, Default::default()); - suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits.push(edits); + suggestion_edits = suggestion_edits.compose(edits); } 30..=59 => { for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { fold_snapshot = new_fold_snapshot; - let (new_suggestion_snapshot, edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits.push(edits); + let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_edits = suggestion_edits.compose(edits); } } _ => buffer.update(cx, |buffer, cx| { @@ -380,11 +383,47 @@ mod tests { let (new_suggestion_snapshot, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits.push(edits); + suggestion_edits = suggestion_edits.compose(edits); log::info!("buffer text: {:?}", buffer_snapshot.text()); log::info!("folds text: {:?}", fold_snapshot.text()); log::info!("suggestions text: {:?}", suggestion_snapshot.text()); + + let mut expected_text = fold_snapshot.text(); + if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { + expected_text.insert_str(suggestion.position.0, &suggestion.text.to_string()); + } + assert_eq!(suggestion_snapshot.text(), expected_text); + + for _ in 0..3 { + let mut end = rng.gen_range(0..=suggestion_snapshot.len().0); + while !expected_text.is_char_boundary(end) { + end += 1; + } + let mut start = rng.gen_range(0..=end); + while !expected_text.is_char_boundary(start) { + start += 1; + } + + let actual_text = suggestion_snapshot + .chunks(SuggestionOffset(start)..SuggestionOffset(end), false, None) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, + &expected_text[start..end], + "incorrect text in range {:?}", + start..end + ); + } + + for edit in suggestion_edits.into_inner() { + prev_suggestion_text.replace_range( + edit.new.start.0..edit.new.start.0 + edit.old_len().0, + &suggestion_snapshot.text()[edit.new.start.0..edit.new.end.0], + ); + } + assert_eq!(prev_suggestion_text, suggestion_snapshot.text()); } } } diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index d0796cfc6acb1d906698cb13af0906c3737765b6..797fb39317bd42596063406a85d4ee6f2d7bf37c 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -31,7 +31,7 @@ const CHUNK_BASE: usize = 16; /// hash being equivalent to hashing all the text contained in the [Rope] at once. pub type RopeFingerprint = HashMatrix; -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default)] pub struct Rope { chunks: SumTree, } @@ -389,6 +389,22 @@ impl fmt::Display for Rope { } } +impl fmt::Debug for Rope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write as _; + + write!(f, "\"")?; + let mut format_string = String::new(); + for chunk in self.chunks() { + write!(&mut format_string, "{:?}", chunk)?; + write!(f, "{}", &format_string[1..format_string.len() - 1])?; + format_string.clear(); + } + write!(f, "\"")?; + Ok(()) + } +} + pub struct Cursor<'a> { rope: &'a Rope, chunks: sum_tree::Cursor<'a, Chunk, usize>, From 3edf83cb999b59b69968b41da97a9f6586a0c49b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 20 Mar 2023 19:45:39 +0100 Subject: [PATCH 07/20] Implement `SuggestionSnapshot::line_len` Co-Authored-By: Max Brunsfeld --- .../editor/src/display_map/suggestion_map.rs | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index f2b7433d355d138bea68295e041f6977772fe6d8..df5ad02dd45429eaf0186367157ae32c7e1d3d73 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -41,6 +41,16 @@ impl AddAssign for SuggestionOffset { #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct SuggestionPoint(pub Point); +impl SuggestionPoint { + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } +} + #[derive(Clone, Debug)] pub struct Suggestion { position: T, @@ -183,6 +193,32 @@ impl SuggestionSnapshot { } } + pub fn line_len(&self, row: u32) -> u32 { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_lines = suggestion.text.max_point(); + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion_lines; + + if row < suggestion_start.row { + self.fold_snapshot.line_len(row) + } else if row > suggestion_end.row { + self.fold_snapshot.line_len(row - suggestion_lines.row) + } else { + let mut len = suggestion.text.line_len(row - suggestion_start.row); + if row == suggestion_start.row { + len += suggestion_start.column; + } + if row == suggestion_end.row { + len += + self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column; + } + len + } + } else { + self.fold_snapshot.line_len(row) + } + } + pub fn chunks<'a>( &'a self, range: Range, @@ -389,21 +425,20 @@ mod tests { log::info!("folds text: {:?}", fold_snapshot.text()); log::info!("suggestions text: {:?}", suggestion_snapshot.text()); - let mut expected_text = fold_snapshot.text(); + let mut expected_text = Rope::from(fold_snapshot.text().as_str()); if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { - expected_text.insert_str(suggestion.position.0, &suggestion.text.to_string()); + expected_text.replace( + suggestion.position.0..suggestion.position.0, + &suggestion.text.to_string(), + ); } - assert_eq!(suggestion_snapshot.text(), expected_text); + assert_eq!(suggestion_snapshot.text(), expected_text.to_string()); - for _ in 0..3 { + for _ in 0..5 { let mut end = rng.gen_range(0..=suggestion_snapshot.len().0); - while !expected_text.is_char_boundary(end) { - end += 1; - } + end = expected_text.clip_offset(end, Bias::Right); let mut start = rng.gen_range(0..=end); - while !expected_text.is_char_boundary(start) { - start += 1; - } + start = expected_text.clip_offset(start, Bias::Right); let actual_text = suggestion_snapshot .chunks(SuggestionOffset(start)..SuggestionOffset(end), false, None) @@ -411,7 +446,7 @@ mod tests { .collect::(); assert_eq!( actual_text, - &expected_text[start..end], + expected_text.slice(start..end).to_string(), "incorrect text in range {:?}", start..end ); @@ -424,6 +459,17 @@ mod tests { ); } assert_eq!(prev_suggestion_text, suggestion_snapshot.text()); + + assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0); + assert_eq!(expected_text.len(), suggestion_snapshot.len().0); + for row in 0..=suggestion_snapshot.max_point().row() { + assert_eq!( + suggestion_snapshot.line_len(row), + expected_text.line_len(row), + "incorrect line len for row {}", + row + ); + } } } } From 1a9dbfa86a2ff4622bbaa4f79a95c3efd62255a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 08:29:33 +0100 Subject: [PATCH 08/20] Add unit test to verify basic properties of the `SuggestionMap` --- .../editor/src/display_map/suggestion_map.rs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index df5ad02dd45429eaf0186367157ae32c7e1d3d73..03b5e878b344ab86ce004ad13a66b52628da22f6 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -131,7 +131,7 @@ impl SuggestionMap { let start = fold_edit.new.start; let end = FoldOffset(start.0 + fold_edit.old_len().0); if let Some(suggestion) = snapshot.suggestion.as_mut() { - if end < suggestion.position { + if end <= suggestion.position { suggestion.position.0 += fold_edit.new_len().0; suggestion.position.0 -= fold_edit.old_len().0; } else if start > suggestion.position { @@ -345,6 +345,52 @@ mod tests { use settings::Settings; use std::env; + #[gpui::test] + fn test_basic(cx: &mut MutableAppContext) { + let buffer = MultiBuffer::build_simple("abcdefghi", cx); + let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); + let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); + let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + assert_eq!(suggestion_snapshot.text(), "abcdefghi"); + + let (suggestion_snapshot, _) = suggestion_map.replace( + Some(Suggestion { + position: 3, + text: "123\n456".into(), + highlight_style: Default::default(), + }), + fold_snapshot, + Default::default(), + ); + assert_eq!(suggestion_snapshot.text(), "abc123\n456defghi"); + + buffer.update(cx, |buffer, cx| { + buffer.edit( + [(0..0, "ABC"), (3..3, "DEF"), (4..4, "GHI"), (9..9, "JKL")], + None, + cx, + ) + }); + let (fold_snapshot, fold_edits) = fold_map.read( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); + assert_eq!(suggestion_snapshot.text(), "ABCabcDEF123\n456dGHIefghiJKL"); + + let (mut fold_map_writer, _, _) = + fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); + let (fold_snapshot, fold_edits) = fold_map_writer.fold([0..3]); + let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); + assert_eq!(suggestion_snapshot.text(), "⋯abcDEF123\n456dGHIefghiJKL"); + + let (mut fold_map_writer, _, _) = + fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); + let (fold_snapshot, fold_edits) = fold_map_writer.fold([6..10]); + let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); + assert_eq!(suggestion_snapshot.text(), "⋯abc⋯GHIefghiJKL"); + } + #[gpui::test(iterations = 100)] fn test_random_suggestions(cx: &mut MutableAppContext, mut rng: StdRng) { cx.set_global(Settings::test(cx)); From ccb61962248f51060611d4f7d56eea46da2fe2c7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 08:56:46 +0100 Subject: [PATCH 09/20] Implement `SuggestionSnapshot::buffer_rows` --- .../editor/src/display_map/suggestion_map.rs | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 03b5e878b344ab86ce004ad13a66b52628da22f6..15e4b1c5fc8c4b94e267101d07c0e713a8443081 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -1,5 +1,5 @@ use super::{ - fold_map::{FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}, + fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}, TextHighlights, }; use crate::ToPoint; @@ -10,6 +10,7 @@ use std::{ cmp, ops::{Add, AddAssign, Range, Sub}, }; +use util::post_inc; pub type SuggestionEdit = Edit; @@ -283,6 +284,34 @@ impl SuggestionSnapshot { } } + pub fn buffer_rows<'a>(&'a self, row: u32) -> SuggestionBufferRows<'a> { + let suggestion_range = if let Some(suggestion) = self.suggestion.as_ref() { + let start = suggestion.position.to_point(&self.fold_snapshot).0; + let end = start + suggestion.text.max_point(); + start.row..end.row + } else { + u32::MAX..u32::MAX + }; + + let fold_buffer_rows = if row <= suggestion_range.start { + self.fold_snapshot.buffer_rows(row) + } else if row > suggestion_range.end { + self.fold_snapshot + .buffer_rows(row - (suggestion_range.end - suggestion_range.start)) + } else { + let mut rows = self.fold_snapshot.buffer_rows(suggestion_range.start); + rows.next(); + rows + }; + + SuggestionBufferRows { + current_row: row, + suggestion_row_start: suggestion_range.start, + suggestion_row_end: suggestion_range.end, + fold_buffer_rows, + } + } + #[cfg(test)] pub fn text(&self) -> String { self.chunks(Default::default()..self.len(), false, None) @@ -336,6 +365,26 @@ impl<'a> Iterator for Chunks<'a> { } } +pub struct SuggestionBufferRows<'a> { + current_row: u32, + suggestion_row_start: u32, + suggestion_row_end: u32, + fold_buffer_rows: FoldBufferRows<'a>, +} + +impl<'a> Iterator for SuggestionBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + let row = post_inc(&mut self.current_row); + if row <= self.suggestion_row_start || row > self.suggestion_row_end { + self.fold_buffer_rows.next() + } else { + Some(None) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -472,13 +521,30 @@ mod tests { log::info!("suggestions text: {:?}", suggestion_snapshot.text()); let mut expected_text = Rope::from(fold_snapshot.text().as_str()); + let mut expected_buffer_rows = fold_snapshot.buffer_rows(0).collect::>(); if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { expected_text.replace( suggestion.position.0..suggestion.position.0, &suggestion.text.to_string(), ); + let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + expected_buffer_rows.splice( + (suggestion_start.row + 1) as usize..(suggestion_start.row + 1) as usize, + (0..suggestion_end.row - suggestion_start.row).map(|_| None), + ); } assert_eq!(suggestion_snapshot.text(), expected_text.to_string()); + for row_start in 0..expected_buffer_rows.len() { + assert_eq!( + suggestion_snapshot + .buffer_rows(row_start as u32) + .collect::>(), + &expected_buffer_rows[row_start..], + "incorrect buffer rows starting at {}", + row_start + ); + } for _ in 0..5 { let mut end = rng.gen_range(0..=suggestion_snapshot.len().0); From 52a156aebec89cd72a0c714df25efecf3506bfcb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 10:39:08 +0100 Subject: [PATCH 10/20] Implement `SuggestionSnapshot::clip_point` --- .../editor/src/display_map/suggestion_map.rs | 96 ++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 15e4b1c5fc8c4b94e267101d07c0e713a8443081..8b193482c736c37ecb6ec2238bd2eb5781277428 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -1,5 +1,5 @@ use super::{ - fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}, + fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, TextHighlights, }; use crate::ToPoint; @@ -194,6 +194,41 @@ impl SuggestionSnapshot { } } + pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + if point.0 <= suggestion_start { + SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) + } else if point.0 > suggestion_end { + let fold_point = self.fold_snapshot.clip_point( + FoldPoint(suggestion_start + (point.0 - suggestion_end)), + bias, + ); + let suggestion_point = suggestion_end + (fold_point.0 - suggestion_start); + if bias == Bias::Left && suggestion_point == suggestion_end { + SuggestionPoint(suggestion_start) + } else { + SuggestionPoint(suggestion_point) + } + } else if bias == Bias::Left || suggestion_start == self.fold_snapshot.max_point().0 { + SuggestionPoint(suggestion_start) + } else { + let fold_point = if self.fold_snapshot.line_len(suggestion_start.row) + > suggestion_start.column + { + FoldPoint(suggestion_start + Point::new(0, 1)) + } else { + FoldPoint(suggestion_start + Point::new(1, 0)) + }; + let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias); + SuggestionPoint(suggestion_end + (clipped_fold_point.0 - suggestion_start)) + } + } else { + SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) + } + } + pub fn line_len(&self, row: u32) -> u32 { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_lines = suggestion.text.max_point(); @@ -392,7 +427,10 @@ mod tests { use gpui::MutableAppContext; use rand::{prelude::StdRng, Rng}; use settings::Settings; - use std::env; + use std::{ + env, + ops::{Bound, RangeBounds}, + }; #[gpui::test] fn test_basic(cx: &mut MutableAppContext) { @@ -582,6 +620,60 @@ mod tests { row ); } + + let mut suggestion_point = SuggestionPoint::default(); + for ch in expected_text.chars() { + let mut bytes = [0; 4]; + for byte in ch.encode_utf8(&mut bytes).as_bytes() { + if *byte == b'\n' { + suggestion_point.0 += Point::new(1, 0); + } else { + suggestion_point.0 += Point::new(0, 1); + } + + let clipped_left_point = + suggestion_snapshot.clip_point(suggestion_point, Bias::Left); + let clipped_right_point = + suggestion_snapshot.clip_point(suggestion_point, Bias::Right); + assert!( + clipped_left_point <= clipped_right_point, + "clipped left point {:?} is greater than clipped right point {:?}", + clipped_left_point, + clipped_right_point + ); + assert_eq!( + clipped_left_point.0, + expected_text.clip_point(clipped_left_point.0, Bias::Left) + ); + assert_eq!( + clipped_right_point.0, + expected_text.clip_point(clipped_right_point.0, Bias::Right) + ); + assert!(clipped_left_point <= suggestion_snapshot.max_point()); + assert!(clipped_right_point <= suggestion_snapshot.max_point()); + + if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { + let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + let invalid_range = ( + Bound::Excluded(suggestion_start), + Bound::Included(suggestion_end), + ); + assert!( + !invalid_range.contains(&clipped_left_point.0), + "clipped left point {:?} is inside invalid suggestion range {:?}", + clipped_left_point, + invalid_range + ); + assert!( + !invalid_range.contains(&clipped_right_point.0), + "clipped right point {:?} is inside invalid suggestion range {:?}", + clipped_right_point, + invalid_range + ); + } + } + } } } } From c0e124a55a23686e17f9af5bac1715f6ba1fec6f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 10:59:53 +0100 Subject: [PATCH 11/20] Implement `SuggestionSnapshot::text_summary_for_range` --- .../editor/src/display_map/suggestion_map.rs | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 8b193482c736c37ecb6ec2238bd2eb5781277428..82d0f57c4b192acf9b2c2045380c16fa9125ae6f 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::ToPoint; use gpui::fonts::HighlightStyle; -use language::{Bias, Chunk, Edit, Patch, Point, Rope}; +use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary}; use parking_lot::Mutex; use std::{ cmp, @@ -255,6 +255,50 @@ impl SuggestionSnapshot { } } + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_lines = suggestion.text.max_point(); + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion_lines; + let mut summary = TextSummary::default(); + + let prefix_range = + cmp::min(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_start); + if prefix_range.start < prefix_range.end { + summary += self.fold_snapshot.text_summary_for_range( + FoldPoint(prefix_range.start)..FoldPoint(prefix_range.end), + ); + } + + let suggestion_range = + cmp::max(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_end); + if suggestion_range.start < suggestion_range.end { + let point_range = suggestion_range.start - suggestion_start + ..suggestion_range.end - suggestion_start; + let offset_range = suggestion.text.point_to_offset(point_range.start) + ..suggestion.text.point_to_offset(point_range.end); + summary += suggestion + .text + .cursor(offset_range.start) + .summary::(offset_range.end); + } + + let suffix_range = cmp::max(range.start.0, suggestion_end)..range.end.0; + if suffix_range.start < suffix_range.end { + let start = suggestion_start + (suffix_range.start - suggestion_end); + let end = suggestion_start + (suffix_range.end - suggestion_end); + summary += self + .fold_snapshot + .text_summary_for_range(FoldPoint(start)..FoldPoint(end)); + } + + summary + } else { + self.fold_snapshot + .text_summary_for_range(FoldPoint(range.start.0)..FoldPoint(range.end.0)) + } + } + pub fn chunks<'a>( &'a self, range: Range, @@ -600,6 +644,13 @@ mod tests { "incorrect text in range {:?}", start..end ); + + let start_point = SuggestionPoint(expected_text.offset_to_point(start)); + let end_point = SuggestionPoint(expected_text.offset_to_point(end)); + assert_eq!( + suggestion_snapshot.text_summary_for_range(start_point..end_point), + expected_text.slice(start..end).summary() + ); } for edit in suggestion_edits.into_inner() { From f829ce56416c1038f04af1b51956de88efba256d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 11:15:59 +0100 Subject: [PATCH 12/20] Implement `SuggestionSnapshot::to_offset` --- .../editor/src/display_map/suggestion_map.rs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 82d0f57c4b192acf9b2c2045380c16fa9125ae6f..8f64cad23dff90a794d60fb0a978e2359f0409e7 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -229,6 +229,27 @@ impl SuggestionSnapshot { } } + pub fn to_offset(&self, point: SuggestionPoint) -> SuggestionOffset { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + + if point.0 <= suggestion_start { + SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) + } else if point.0 > suggestion_end { + let fold_offset = FoldPoint(suggestion_start + (point.0 - suggestion_end)) + .to_offset(&self.fold_snapshot); + SuggestionOffset(fold_offset.0 + suggestion.text.len()) + } else { + let offset_in_suggestion = + suggestion.text.point_to_offset(point.0 - suggestion_start); + SuggestionOffset(suggestion.position.0 + offset_in_suggestion) + } + } else { + SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) + } + } + pub fn line_len(&self, row: u32) -> u32 { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_lines = suggestion.text.max_point(); @@ -257,9 +278,8 @@ impl SuggestionSnapshot { pub fn text_summary_for_range(&self, range: Range) -> TextSummary { if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_lines = suggestion.text.max_point(); let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion_lines; + let suggestion_end = suggestion_start + suggestion.text.max_point(); let mut summary = TextSummary::default(); let prefix_range = @@ -299,6 +319,12 @@ impl SuggestionSnapshot { } } + pub fn chars_at(&self, start: SuggestionPoint) -> impl '_ + Iterator { + let start = self.to_offset(start); + self.chunks(start..self.len(), false, None) + .flat_map(|chunk| chunk.text.chars()) + } + pub fn chunks<'a>( &'a self, range: Range, @@ -673,9 +699,18 @@ mod tests { } let mut suggestion_point = SuggestionPoint::default(); + let mut suggestion_offset = SuggestionOffset::default(); for ch in expected_text.chars() { + assert_eq!( + suggestion_snapshot.to_offset(suggestion_point), + suggestion_offset, + "invalid to_offset({:?})", + suggestion_point + ); + let mut bytes = [0; 4]; for byte in ch.encode_utf8(&mut bytes).as_bytes() { + suggestion_offset.0 += 1; if *byte == b'\n' { suggestion_point.0 += Point::new(1, 0); } else { From d448a5cb5c0eab38991213ab50937c0e73ede582 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 11:27:55 +0100 Subject: [PATCH 13/20] Implement `SuggestionSnapshot::to_point` --- .../editor/src/display_map/suggestion_map.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 8f64cad23dff90a794d60fb0a978e2359f0409e7..7eac5a1f6acc1ed3ce307d0b5522a65bd325bc3c 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -250,6 +250,32 @@ impl SuggestionSnapshot { } } + pub fn to_point(&self, offset: SuggestionOffset) -> SuggestionPoint { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_point_start = suggestion.position.to_point(&self.fold_snapshot).0; + if offset.0 <= suggestion.position.0 { + SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) + } else if offset.0 > (suggestion.position.0 + suggestion.text.len()) { + let fold_point = FoldOffset(offset.0 - suggestion.text.len()) + .to_point(&self.fold_snapshot) + .0; + + SuggestionPoint( + suggestion_point_start + + suggestion.text.max_point() + + (fold_point - suggestion_point_start), + ) + } else { + let point_in_suggestion = suggestion + .text + .offset_to_point(offset.0 - suggestion.position.0); + SuggestionPoint(suggestion_point_start + point_in_suggestion) + } + } else { + SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) + } + } + pub fn line_len(&self, row: u32) -> u32 { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_lines = suggestion.text.max_point(); @@ -707,6 +733,12 @@ mod tests { "invalid to_offset({:?})", suggestion_point ); + assert_eq!( + suggestion_snapshot.to_point(suggestion_offset), + suggestion_point, + "invalid to_point({:?})", + suggestion_offset + ); let mut bytes = [0; 4]; for byte in ch.encode_utf8(&mut bytes).as_bytes() { From 35830a0271db4e081d6e8a5dbec10dafe75d5b19 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 11:39:29 +0100 Subject: [PATCH 14/20] Implement `SuggestionSnapshot::to_{fold,suggestion}_point` --- .../editor/src/display_map/suggestion_map.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 7eac5a1f6acc1ed3ce307d0b5522a65bd325bc3c..d15ada5f375064671132bd9b99f52143cad4dfcb 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -276,6 +276,38 @@ impl SuggestionSnapshot { } } + pub fn to_fold_point(&self, point: SuggestionPoint) -> FoldPoint { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + + if point.0 <= suggestion_start { + FoldPoint(point.0) + } else if point.0 > suggestion_end { + FoldPoint(suggestion_start + (point.0 - suggestion_end)) + } else { + FoldPoint(suggestion_start) + } + } else { + FoldPoint(point.0) + } + } + + pub fn to_suggestion_point(&self, point: FoldPoint) -> SuggestionPoint { + if let Some(suggestion) = self.suggestion.as_ref() { + let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; + + if point.0 <= suggestion_start { + SuggestionPoint(point.0) + } else { + let suggestion_end = suggestion_start + suggestion.text.max_point(); + SuggestionPoint(suggestion_end + (point.0 - suggestion_start)) + } + } else { + SuggestionPoint(point.0) + } + } + pub fn line_len(&self, row: u32) -> u32 { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_lines = suggestion.text.max_point(); @@ -739,6 +771,11 @@ mod tests { "invalid to_point({:?})", suggestion_offset ); + assert_eq!( + suggestion_snapshot + .to_suggestion_point(suggestion_snapshot.to_fold_point(suggestion_point)), + suggestion_snapshot.clip_point(suggestion_point, Bias::Left), + ); let mut bytes = [0; 4]; for byte in ch.encode_utf8(&mut bytes).as_bytes() { From 3d165f705ffde5b6e34cc839f5a95321e73774aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 11:51:06 +0100 Subject: [PATCH 15/20] Extract a `SuggestionMap::randomly_mutate` method --- .../editor/src/display_map/suggestion_map.rs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index d15ada5f375064671132bd9b99f52143cad4dfcb..49cc64a052c360c0eefeb7744fc51603b4c4119a 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -635,25 +635,7 @@ mod tests { let mut buffer_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=29 => { - let new_suggestion = if rng.gen_bool(0.3) { - None - } else { - let index = rng.gen_range(0..=buffer_snapshot.len()); - let len = rng.gen_range(0..30); - Some(Suggestion { - position: index, - text: util::RandomCharIter::new(&mut rng) - .take(len) - .collect::() - .as_str() - .into(), - highlight_style: Default::default(), - }) - }; - - log::info!("replacing suggestion with {:?}", new_suggestion); - let (_, edits) = - suggestion_map.replace(new_suggestion, fold_snapshot, Default::default()); + let (_, edits) = suggestion_map.randomly_mutate(&mut rng); suggestion_edits = suggestion_edits.compose(edits); } 30..=59 => { @@ -831,4 +813,28 @@ mod tests { } } } + + impl SuggestionMap { + fn randomly_mutate(&self, rng: &mut impl Rng) -> (SuggestionSnapshot, Vec) { + let fold_snapshot = self.0.lock().fold_snapshot.clone(); + let new_suggestion = if rng.gen_bool(0.3) { + None + } else { + let index = rng.gen_range(0..=fold_snapshot.buffer_snapshot().len()); + let len = rng.gen_range(0..30); + Some(Suggestion { + position: index, + text: util::RandomCharIter::new(rng) + .take(len) + .collect::() + .as_str() + .into(), + highlight_style: Default::default(), + }) + }; + + log::info!("replacing suggestion with {:?}", new_suggestion); + self.replace(new_suggestion, fold_snapshot, Default::default()) + } + } } From d1978a719ba5a05a616351f6217f0d9508250682 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 12:47:04 +0100 Subject: [PATCH 16/20] Add a `version` field to `SuggestionSnapshot` --- crates/editor/src/display_map/suggestion_map.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 49cc64a052c360c0eefeb7744fc51603b4c4119a..499a9fa35483048730f9d01dcdc14a7e05fc56a9 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -2,7 +2,7 @@ use super::{ fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, TextHighlights, }; -use crate::ToPoint; +use crate::{MultiBufferSnapshot, ToPoint}; use gpui::fonts::HighlightStyle; use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary}; use parking_lot::Mutex; @@ -66,6 +66,7 @@ impl SuggestionMap { let snapshot = SuggestionSnapshot { fold_snapshot, suggestion: None, + version: 0, }; (Self(Mutex::new(snapshot.clone())), snapshot) } @@ -115,6 +116,7 @@ impl SuggestionMap { } snapshot.suggestion = new_suggestion; + snapshot.version += 1; (snapshot.clone(), patch.into_inner()) } @@ -124,6 +126,11 @@ impl SuggestionMap { fold_edits: Vec, ) -> (SuggestionSnapshot, Vec) { let mut snapshot = self.0.lock(); + + if snapshot.fold_snapshot.version != fold_snapshot.version { + snapshot.version += 1; + } + let mut suggestion_edits = Vec::new(); let mut suggestion_old_len = 0; @@ -168,9 +175,14 @@ impl SuggestionMap { pub struct SuggestionSnapshot { fold_snapshot: FoldSnapshot, suggestion: Option>, + pub version: usize, } impl SuggestionSnapshot { + pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { + self.fold_snapshot.buffer_snapshot() + } + pub fn max_point(&self) -> SuggestionPoint { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_point = suggestion.position.to_point(&self.fold_snapshot); From 9c8732a3553163fe9a869a99a24afc7620d9a5e3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 13:44:04 +0100 Subject: [PATCH 17/20] Integrate `SuggestionMap` into the rest of `DisplayMap` --- crates/editor/src/display_map.rs | 138 ++++++++++-------- crates/editor/src/display_map/block_map.rs | 61 +++++--- crates/editor/src/display_map/fold_map.rs | 10 -- .../editor/src/display_map/suggestion_map.rs | 59 +++----- crates/editor/src/display_map/tab_map.rs | 134 ++++++++++------- crates/editor/src/display_map/wrap_map.rs | 75 ++++++---- 6 files changed, 259 insertions(+), 218 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 777fa2e9f51566487149c217f7860a646a7639e5..30388291548d5a36d0da946c3abe9d4fe79d3c69 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -16,8 +16,9 @@ use gpui::{ use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; use settings::Settings; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; +use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; -use tab_map::TabMap; +use tab_map::{TabMap, TabSnapshot}; use wrap_map::WrapMap; pub use block_map::{ @@ -25,8 +26,6 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; -use self::tab_map::TabSnapshot; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -43,6 +42,7 @@ pub struct DisplayMap { buffer: ModelHandle, buffer_subscription: BufferSubscription, fold_map: FoldMap, + suggestion_map: SuggestionMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, @@ -68,6 +68,7 @@ impl DisplayMap { let tab_size = Self::tab_size(&buffer, cx); let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); + let (suggestion_map, snapshot) = SuggestionMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); @@ -76,6 +77,7 @@ impl DisplayMap { buffer, buffer_subscription, fold_map, + suggestion_map, tab_map, wrap_map, block_map, @@ -87,21 +89,25 @@ impl DisplayMap { pub fn snapshot(&self, cx: &mut ModelContext) -> DisplaySnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); + let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); + let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits, tab_size); - let (wraps_snapshot, edits) = self + let (tab_snapshot, edits) = self + .tab_map + .sync(suggestion_snapshot.clone(), edits, tab_size); + let (wrap_snapshot, edits) = self .wrap_map - .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx)); - let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits); + .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); + let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits); DisplaySnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(cx), - folds_snapshot, - tabs_snapshot, - wraps_snapshot, - blocks_snapshot, + fold_snapshot, + suggestion_snapshot, + tab_snapshot, + wrap_snapshot, + block_snapshot, text_highlights: self.text_highlights.clone(), clip_at_line_ends: self.clip_at_line_ends, } @@ -125,12 +131,14 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -148,12 +156,14 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -170,6 +180,7 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -187,6 +198,7 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -249,10 +261,11 @@ impl DisplayMap { pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, - folds_snapshot: fold_map::FoldSnapshot, - tabs_snapshot: tab_map::TabSnapshot, - wraps_snapshot: wrap_map::WrapSnapshot, - blocks_snapshot: block_map::BlockSnapshot, + fold_snapshot: fold_map::FoldSnapshot, + suggestion_snapshot: suggestion_map::SuggestionSnapshot, + tab_snapshot: tab_map::TabSnapshot, + wrap_snapshot: wrap_map::WrapSnapshot, + block_snapshot: block_map::BlockSnapshot, text_highlights: TextHighlights, clip_at_line_ends: bool, } @@ -260,7 +273,7 @@ pub struct DisplaySnapshot { impl DisplaySnapshot { #[cfg(test)] pub fn fold_count(&self) -> usize { - self.folds_snapshot.fold_count() + self.fold_snapshot.fold_count() } pub fn is_empty(&self) -> bool { @@ -268,7 +281,7 @@ impl DisplaySnapshot { } pub fn buffer_rows(&self, start_row: u32) -> DisplayBufferRows { - self.blocks_snapshot.buffer_rows(start_row) + self.block_snapshot.buffer_rows(start_row) } pub fn max_buffer_row(&self) -> u32 { @@ -277,9 +290,9 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left); + let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Left); *fold_point.column_mut() = 0; - point = fold_point.to_buffer_point(&self.folds_snapshot); + point = fold_point.to_buffer_point(&self.fold_snapshot); let mut display_point = self.point_to_display_point(point, Bias::Left); *display_point.column_mut() = 0; @@ -293,9 +306,9 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right); - *fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row()); - point = fold_point.to_buffer_point(&self.folds_snapshot); + let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Right); + *fold_point.column_mut() = self.fold_snapshot.line_len(fold_point.row()); + point = fold_point.to_buffer_point(&self.fold_snapshot); let mut display_point = self.point_to_display_point(point, Bias::Right); *display_point.column_mut() = self.line_len(display_point.row()); @@ -325,28 +338,30 @@ impl DisplaySnapshot { } fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { - let fold_point = self.folds_snapshot.to_fold_point(point, bias); - let tab_point = self.tabs_snapshot.to_tab_point(fold_point); - let wrap_point = self.wraps_snapshot.tab_point_to_wrap_point(tab_point); - let block_point = self.blocks_snapshot.to_block_point(wrap_point); + let fold_point = self.fold_snapshot.to_fold_point(point, bias); + let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); + let tab_point = self.tab_snapshot.to_tab_point(suggestion_point); + let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); + let block_point = self.block_snapshot.to_block_point(wrap_point); DisplayPoint(block_point) } fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { let block_point = point.0; - let wrap_point = self.blocks_snapshot.to_wrap_point(block_point); - let tab_point = self.wraps_snapshot.to_tab_point(wrap_point); - let fold_point = self.tabs_snapshot.to_fold_point(tab_point, bias).0; - fold_point.to_buffer_point(&self.folds_snapshot) + let wrap_point = self.block_snapshot.to_wrap_point(block_point); + let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); + let suggestion_point = self.tab_snapshot.to_suggestion_point(tab_point, bias).0; + let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); + fold_point.to_buffer_point(&self.fold_snapshot) } pub fn max_point(&self) -> DisplayPoint { - DisplayPoint(self.blocks_snapshot.max_point()) + DisplayPoint(self.block_snapshot.max_point()) } /// Returns text chunks starting at the given display row until the end of the file pub fn text_chunks(&self, display_row: u32) -> impl Iterator { - self.blocks_snapshot + self.block_snapshot .chunks(display_row..self.max_point().row() + 1, false, None) .map(|h| h.text) } @@ -354,7 +369,7 @@ impl DisplaySnapshot { /// Returns text chunks starting at the end of the given display row in reverse until the start of the file pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { (0..=display_row).into_iter().rev().flat_map(|row| { - self.blocks_snapshot + self.block_snapshot .chunks(row..row + 1, false, None) .map(|h| h.text) .collect::>() @@ -364,7 +379,7 @@ impl DisplaySnapshot { } pub fn chunks(&self, display_rows: Range, language_aware: bool) -> DisplayChunks<'_> { - self.blocks_snapshot + self.block_snapshot .chunks(display_rows, language_aware, Some(&self.text_highlights)) } @@ -372,7 +387,7 @@ impl DisplaySnapshot { &self, mut point: DisplayPoint, ) -> impl Iterator + '_ { - point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left)); + point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); self.text_chunks(point.row()) .flat_map(str::chars) .skip_while({ @@ -399,7 +414,7 @@ impl DisplaySnapshot { &self, mut point: DisplayPoint, ) -> impl Iterator + '_ { - point = DisplayPoint(self.blocks_snapshot.clip_point(point.0, Bias::Left)); + point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); self.reverse_text_chunks(point.row()) .flat_map(|chunk| chunk.chars().rev()) .skip_while({ @@ -513,7 +528,7 @@ impl DisplaySnapshot { } pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { - let mut clipped = self.blocks_snapshot.clip_point(point.0, bias); + let mut clipped = self.block_snapshot.clip_point(point.0, bias); if self.clip_at_line_ends { clipped = self.clip_at_line_end(DisplayPoint(clipped)).0 } @@ -524,7 +539,7 @@ impl DisplaySnapshot { let mut point = point.0; if point.column == self.line_len(point.row) { point.column = point.column.saturating_sub(1); - point = self.blocks_snapshot.clip_point(point, Bias::Left); + point = self.block_snapshot.clip_point(point, Bias::Left); } DisplayPoint(point) } @@ -533,34 +548,34 @@ impl DisplaySnapshot { where T: ToOffset, { - self.folds_snapshot.folds_in_range(range) + self.fold_snapshot.folds_in_range(range) } pub fn blocks_in_range( &self, rows: Range, ) -> impl Iterator { - self.blocks_snapshot.blocks_in_range(rows) + self.block_snapshot.blocks_in_range(rows) } pub fn intersects_fold(&self, offset: T) -> bool { - self.folds_snapshot.intersects_fold(offset) + self.fold_snapshot.intersects_fold(offset) } pub fn is_line_folded(&self, buffer_row: u32) -> bool { - self.folds_snapshot.is_line_folded(buffer_row) + self.fold_snapshot.is_line_folded(buffer_row) } pub fn is_block_line(&self, display_row: u32) -> bool { - self.blocks_snapshot.is_block_line(display_row) + self.block_snapshot.is_block_line(display_row) } pub fn soft_wrap_indent(&self, display_row: u32) -> Option { let wrap_row = self - .blocks_snapshot + .block_snapshot .to_wrap_point(BlockPoint::new(display_row, 0)) .row(); - self.wraps_snapshot.soft_wrap_indent(wrap_row) + self.wrap_snapshot.soft_wrap_indent(wrap_row) } pub fn text(&self) -> String { @@ -614,18 +629,18 @@ impl DisplaySnapshot { } }), buffer.line_len(buffer_row) as usize, // Never collapse - self.tabs_snapshot.tab_size, + self.tab_snapshot.tab_size, ); (indent_size as u32, is_blank) } pub fn line_len(&self, row: u32) -> u32 { - self.blocks_snapshot.line_len(row) + self.block_snapshot.line_len(row) } pub fn longest_row(&self) -> u32 { - self.blocks_snapshot.longest_row() + self.block_snapshot.longest_row() } pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option { @@ -742,10 +757,11 @@ impl DisplayPoint { } pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { - let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0); - let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point); - let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; - unexpanded_point.to_buffer_offset(&map.folds_snapshot) + let wrap_point = map.block_snapshot.to_wrap_point(self.0); + let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); + let suggestion_point = map.tab_snapshot.to_suggestion_point(tab_point, bias).0; + let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); + fold_point.to_buffer_offset(&map.fold_snapshot) } } @@ -868,10 +884,10 @@ pub mod tests { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text()); - log::info!("fold text: {:?}", snapshot.folds_snapshot.text()); - log::info!("tab text: {:?}", snapshot.tabs_snapshot.text()); - log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text()); - log::info!("block text: {:?}", snapshot.blocks_snapshot.text()); + log::info!("fold text: {:?}", snapshot.fold_snapshot.text()); + log::info!("tab text: {:?}", snapshot.tab_snapshot.text()); + log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text()); + log::info!("block text: {:?}", snapshot.block_snapshot.text()); log::info!("display text: {:?}", snapshot.text()); for _i in 0..operations { @@ -976,10 +992,10 @@ pub mod tests { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); fold_count = snapshot.fold_count(); log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text()); - log::info!("fold text: {:?}", snapshot.folds_snapshot.text()); - log::info!("tab text: {:?}", snapshot.tabs_snapshot.text()); - log::info!("wrap text: {:?}", snapshot.wraps_snapshot.text()); - log::info!("block text: {:?}", snapshot.blocks_snapshot.text()); + log::info!("fold text: {:?}", snapshot.fold_snapshot.text()); + log::info!("tab text: {:?}", snapshot.tab_snapshot.text()); + log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text()); + log::info!("block text: {:?}", snapshot.block_snapshot.text()); log::info!("display text: {:?}", snapshot.text()); // Line boundaries diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index fffe20bb219970028b6aa8ef08b2fe677a8b1c1e..adea668179555db73e28cb99a45396a350aedf0d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -989,6 +989,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { #[cfg(test)] mod tests { use super::*; + use crate::display_map::suggestion_map::SuggestionMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; @@ -1029,9 +1030,10 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot, 1.try_into().unwrap()); - let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx); + let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); @@ -1173,12 +1175,14 @@ mod tests { buffer.snapshot(cx) }); - let (folds_snapshot, fold_edits) = + let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot, subscription.consume().into_inner()); - let (tabs_snapshot, tab_edits) = - tab_map.sync(folds_snapshot, fold_edits, 4.try_into().unwrap()); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); + let (tab_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - wrap_map.sync(tabs_snapshot, tab_edits, cx) + wrap_map.sync(tab_snapshot, tab_edits, cx) }); let snapshot = block_map.read(wraps_snapshot, wrap_edits); assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n"); @@ -1201,9 +1205,10 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, tabs_snapshot) = TabMap::new(folds_snapshot, 1.try_into().unwrap()); - let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); + let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); @@ -1272,10 +1277,11 @@ mod tests { }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot, tab_size); + let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); + let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, tab_size); let (wrap_map, wraps_snapshot) = - WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); + WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( wraps_snapshot, buffer_start_header_height, @@ -1326,12 +1332,14 @@ mod tests { }) .collect::>(); - let (folds_snapshot, fold_edits) = + let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), vec![]); - let (tabs_snapshot, tab_edits) = - tab_map.sync(folds_snapshot, fold_edits, tab_size); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); + let (tab_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - wrap_map.sync(tabs_snapshot, tab_edits, cx) + wrap_map.sync(tab_snapshot, tab_edits, cx) }); let mut block_map = block_map.write(wraps_snapshot, wrap_edits); let block_ids = block_map.insert(block_properties.clone()); @@ -1349,12 +1357,14 @@ mod tests { }) .collect(); - let (folds_snapshot, fold_edits) = + let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), vec![]); - let (tabs_snapshot, tab_edits) = - tab_map.sync(folds_snapshot, fold_edits, tab_size); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); + let (tab_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - wrap_map.sync(tabs_snapshot, tab_edits, cx) + wrap_map.sync(tab_snapshot, tab_edits, cx) }); let mut block_map = block_map.write(wraps_snapshot, wrap_edits); block_map.remove(block_ids_to_remove); @@ -1371,10 +1381,13 @@ mod tests { } } - let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits, tab_size); + let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); + let (tab_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - wrap_map.sync(tabs_snapshot, tab_edits, cx) + wrap_map.sync(tab_snapshot, tab_edits, cx) }); let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); assert_eq!( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 4e624c824ce2b3cbe66720313419ddade6f010da..fa1de81e0da042827002c9638232926afb79ca03 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -29,10 +29,6 @@ impl FoldPoint { self.0.row } - pub fn column(self) -> u32 { - self.0.column - } - pub fn row_mut(&mut self) -> &mut u32 { &mut self.0.row } @@ -655,12 +651,6 @@ impl FoldSnapshot { false } - pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { - let start = start.to_offset(self); - self.chunks(start..self.len(), false, None) - .flat_map(|chunk| chunk.text.chars()) - } - pub fn chunks<'a>( &'a self, range: Range, diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 499a9fa35483048730f9d01dcdc14a7e05fc56a9..3e8014724850bccf3c17553a12838a574cf59446 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -43,6 +43,10 @@ impl AddAssign for SuggestionOffset { pub struct SuggestionPoint(pub Point); impl SuggestionPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + pub fn row(self) -> u32 { self.0.row } @@ -173,8 +177,8 @@ impl SuggestionMap { #[derive(Clone)] pub struct SuggestionSnapshot { - fold_snapshot: FoldSnapshot, - suggestion: Option>, + pub fold_snapshot: FoldSnapshot, + pub suggestion: Option>, pub version: usize, } @@ -320,32 +324,6 @@ impl SuggestionSnapshot { } } - pub fn line_len(&self, row: u32) -> u32 { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_lines = suggestion.text.max_point(); - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion_lines; - - if row < suggestion_start.row { - self.fold_snapshot.line_len(row) - } else if row > suggestion_end.row { - self.fold_snapshot.line_len(row - suggestion_lines.row) - } else { - let mut len = suggestion.text.line_len(row - suggestion_start.row); - if row == suggestion_start.row { - len += suggestion_start.column; - } - if row == suggestion_end.row { - len += - self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column; - } - len - } - } else { - self.fold_snapshot.line_len(row) - } - } - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; @@ -400,7 +378,7 @@ impl SuggestionSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - ) -> Chunks<'a> { + ) -> SuggestionChunks<'a> { if let Some(suggestion) = self.suggestion.as_ref() { let suggestion_range = suggestion.position.0..suggestion.position.0 + suggestion.text.len(); @@ -439,14 +417,14 @@ impl SuggestionSnapshot { None }; - Chunks { + SuggestionChunks { prefix_chunks, suggestion_chunks, suffix_chunks, highlight_style: suggestion.highlight_style, } } else { - Chunks { + SuggestionChunks { prefix_chunks: Some(self.fold_snapshot.chunks( FoldOffset(range.start.0)..FoldOffset(range.end.0), language_aware, @@ -495,14 +473,14 @@ impl SuggestionSnapshot { } } -pub struct Chunks<'a> { +pub struct SuggestionChunks<'a> { prefix_chunks: Option>, suggestion_chunks: Option>, suffix_chunks: Option>, highlight_style: HighlightStyle, } -impl<'a> Iterator for Chunks<'a> { +impl<'a> Iterator for SuggestionChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { @@ -540,6 +518,7 @@ impl<'a> Iterator for Chunks<'a> { } } +#[derive(Clone)] pub struct SuggestionBufferRows<'a> { current_row: u32, suggestion_row_start: u32, @@ -741,14 +720,6 @@ mod tests { assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0); assert_eq!(expected_text.len(), suggestion_snapshot.len().0); - for row in 0..=suggestion_snapshot.max_point().row() { - assert_eq!( - suggestion_snapshot.line_len(row), - expected_text.line_len(row), - "incorrect line len for row {}", - row - ); - } let mut suggestion_point = SuggestionPoint::default(); let mut suggestion_offset = SuggestionOffset::default(); @@ -827,7 +798,10 @@ mod tests { } impl SuggestionMap { - fn randomly_mutate(&self, rng: &mut impl Rng) -> (SuggestionSnapshot, Vec) { + pub fn randomly_mutate( + &self, + rng: &mut impl Rng, + ) -> (SuggestionSnapshot, Vec) { let fold_snapshot = self.0.lock().fold_snapshot.clone(); let new_suggestion = if rng.gen_bool(0.3) { None @@ -838,6 +812,7 @@ mod tests { position: index, text: util::RandomCharIter::new(rng) .take(len) + .filter(|ch| *ch != '\r') .collect::() .as_str() .into(), diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 5a5aae6135055e3d54041ae07c2774439f51fa80..45c92ea7b84ec0809213a6dc80238030d4bd1d79 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,5 +1,5 @@ use super::{ - fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot}, + suggestion_map::{self, SuggestionChunks, SuggestionEdit, SuggestionPoint, SuggestionSnapshot}, TextHighlights, }; use crate::MultiBufferSnapshot; @@ -11,9 +11,9 @@ use sum_tree::Bias; pub struct TabMap(Mutex); impl TabMap { - pub fn new(input: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + pub fn new(input: SuggestionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { let snapshot = TabSnapshot { - fold_snapshot: input, + suggestion_snapshot: input, tab_size, version: 0, }; @@ -22,37 +22,37 @@ impl TabMap { pub fn sync( &self, - fold_snapshot: FoldSnapshot, - mut fold_edits: Vec, + suggestion_snapshot: SuggestionSnapshot, + mut suggestion_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); let mut new_snapshot = TabSnapshot { - fold_snapshot, + suggestion_snapshot, tab_size, version: old_snapshot.version, }; - if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version { + if old_snapshot.suggestion_snapshot.version != new_snapshot.suggestion_snapshot.version { new_snapshot.version += 1; } - let old_max_offset = old_snapshot.fold_snapshot.len(); - let mut tab_edits = Vec::with_capacity(fold_edits.len()); + let old_max_offset = old_snapshot.suggestion_snapshot.len(); + let mut tab_edits = Vec::with_capacity(suggestion_edits.len()); if old_snapshot.tab_size == new_snapshot.tab_size { - for fold_edit in &mut fold_edits { + for suggestion_edit in &mut suggestion_edits { let mut delta = 0; - for chunk in old_snapshot.fold_snapshot.chunks( - fold_edit.old.end..old_max_offset, + for chunk in old_snapshot.suggestion_snapshot.chunks( + suggestion_edit.old.end..old_max_offset, false, None, ) { let patterns: &[_] = &['\t', '\n']; if let Some(ix) = chunk.text.find(patterns) { if &chunk.text[ix..ix + 1] == "\t" { - fold_edit.old.end.0 += delta + ix + 1; - fold_edit.new.end.0 += delta + ix + 1; + suggestion_edit.old.end.0 += delta + ix + 1; + suggestion_edit.new.end.0 += delta + ix + 1; } break; @@ -63,24 +63,32 @@ impl TabMap { } let mut ix = 1; - while ix < fold_edits.len() { - let (prev_edits, next_edits) = fold_edits.split_at_mut(ix); + while ix < suggestion_edits.len() { + let (prev_edits, next_edits) = suggestion_edits.split_at_mut(ix); let prev_edit = prev_edits.last_mut().unwrap(); let edit = &next_edits[0]; if prev_edit.old.end >= edit.old.start { prev_edit.old.end = edit.old.end; prev_edit.new.end = edit.new.end; - fold_edits.remove(ix); + suggestion_edits.remove(ix); } else { ix += 1; } } - for fold_edit in fold_edits { - let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); - let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); - let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); - let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); + for suggestion_edit in suggestion_edits { + let old_start = old_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.old.start); + let old_end = old_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.old.end); + let new_start = new_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.new.start); + let new_end = new_snapshot + .suggestion_snapshot + .to_point(suggestion_edit.new.end); tab_edits.push(TabEdit { old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end), @@ -101,14 +109,14 @@ impl TabMap { #[derive(Clone)] pub struct TabSnapshot { - pub fold_snapshot: FoldSnapshot, + pub suggestion_snapshot: SuggestionSnapshot, pub tab_size: NonZeroU32, pub version: usize, } impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.fold_snapshot.buffer_snapshot() + self.suggestion_snapshot.buffer_snapshot() } pub fn line_len(&self, row: u32) -> u32 { @@ -132,10 +140,10 @@ impl TabSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let input_start = self.to_fold_point(range.start, Bias::Left).0; - let input_end = self.to_fold_point(range.end, Bias::Right).0; + let input_start = self.to_suggestion_point(range.start, Bias::Left).0; + let input_end = self.to_suggestion_point(range.end, Bias::Right).0; let input_summary = self - .fold_snapshot + .suggestion_snapshot .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; @@ -182,12 +190,11 @@ impl TabSnapshot { text_highlights: Option<&'a TextHighlights>, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = - self.to_fold_point(range.start, Bias::Left); - let input_start = input_start.to_offset(&self.fold_snapshot); + self.to_suggestion_point(range.start, Bias::Left); + let input_start = self.suggestion_snapshot.to_offset(input_start); let input_end = self - .to_fold_point(range.end, Bias::Right) - .0 - .to_offset(&self.fold_snapshot); + .suggestion_snapshot + .to_offset(self.to_suggestion_point(range.end, Bias::Right).0); let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 { (range.end.column() - range.start.column()) as usize } else { @@ -195,7 +202,7 @@ impl TabSnapshot { }; TabChunks { - fold_chunks: self.fold_snapshot.chunks( + suggestion_chunks: self.suggestion_snapshot.chunks( input_start..input_end, language_aware, text_highlights, @@ -212,8 +219,8 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows { - self.fold_snapshot.buffer_rows(row) + pub fn buffer_rows(&self, row: u32) -> suggestion_map::SuggestionBufferRows { + self.suggestion_snapshot.buffer_rows(row) } #[cfg(test)] @@ -224,42 +231,55 @@ impl TabSnapshot { } pub fn max_point(&self) -> TabPoint { - self.to_tab_point(self.fold_snapshot.max_point()) + self.to_tab_point(self.suggestion_snapshot.max_point()) } pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { self.to_tab_point( - self.fold_snapshot - .clip_point(self.to_fold_point(point, bias).0, bias), + self.suggestion_snapshot + .clip_point(self.to_suggestion_point(point, bias).0, bias), ) } - pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint { - let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0)); + pub fn to_tab_point(&self, input: SuggestionPoint) -> TabPoint { + let chars = self + .suggestion_snapshot + .chars_at(SuggestionPoint::new(input.row(), 0)); let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size); TabPoint::new(input.row(), expanded as u32) } - pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) { - let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); + pub fn to_suggestion_point( + &self, + output: TabPoint, + bias: Bias, + ) -> (SuggestionPoint, usize, usize) { + let chars = self + .suggestion_snapshot + .chars_at(SuggestionPoint::new(output.row(), 0)); let expanded = output.column() as usize; let (collapsed, expanded_char_column, to_next_stop) = Self::collapse_tabs(chars, expanded, bias, self.tab_size); ( - FoldPoint::new(output.row(), collapsed as u32), + SuggestionPoint::new(output.row(), collapsed as u32), expanded_char_column, to_next_stop, ) } pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { - self.to_tab_point(self.fold_snapshot.to_fold_point(point, bias)) + let fold_point = self + .suggestion_snapshot + .fold_snapshot + .to_fold_point(point, bias); + let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); + self.to_tab_point(suggestion_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { - self.to_fold_point(point, bias) - .0 - .to_buffer_point(&self.fold_snapshot) + let suggestion_point = self.to_suggestion_point(point, bias).0; + let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); + fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) } pub fn expand_tabs( @@ -412,7 +432,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { const SPACES: &str = " "; pub struct TabChunks<'a> { - fold_chunks: fold_map::FoldChunks<'a>, + suggestion_chunks: SuggestionChunks<'a>, chunk: Chunk<'a>, column: usize, output_position: Point, @@ -426,7 +446,7 @@ impl<'a> Iterator for TabChunks<'a> { fn next(&mut self) -> Option { if self.chunk.text.is_empty() { - if let Some(chunk) = self.fold_chunks.next() { + if let Some(chunk) = self.suggestion_chunks.next() { self.chunk = chunk; if self.skip_leading_tab { self.chunk.text = &self.chunk.text[1..]; @@ -482,7 +502,10 @@ impl<'a> Iterator for TabChunks<'a> { #[cfg(test)] mod tests { use super::*; - use crate::{display_map::fold_map::FoldMap, MultiBuffer}; + use crate::{ + display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, + MultiBuffer, + }; use rand::{prelude::StdRng, Rng}; #[test] @@ -518,10 +541,13 @@ mod tests { let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone()); fold_map.randomly_mutate(&mut rng); - let (folds_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); - log::info!("FoldMap text: {:?}", folds_snapshot.text()); + let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); + log::info!("FoldMap text: {:?}", fold_snapshot.text()); + let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); + let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); + log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + let (_, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size); let text = text::Rope::from(tabs_snapshot.text().as_str()); log::info!( "TabMap text (tab size: {}): {:?}", @@ -557,7 +583,7 @@ mod tests { ); let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); - if tab_size.get() > 1 && folds_snapshot.text().contains('\t') { + if tab_size.get() > 1 && suggestion_snapshot.text().contains('\t') { actual_summary.longest_row = expected_summary.longest_row; actual_summary.longest_row_chars = expected_summary.longest_row_chars; } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 0d5fb878e583a0269d33b8742e8c18733d19a8fb..f0d10ad42339eb37a7e6ae292ee675c4990e8ad8 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,5 @@ use super::{ - fold_map, + suggestion_map::SuggestionBufferRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, TextHighlights, }; @@ -64,7 +64,7 @@ pub struct WrapChunks<'a> { #[derive(Clone)] pub struct WrapBufferRows<'a> { - input_buffer_rows: fold_map::FoldBufferRows<'a>, + input_buffer_rows: SuggestionBufferRows<'a>, input_buffer_row: Option, output_row: u32, soft_wrapped: bool, @@ -755,16 +755,24 @@ impl WrapSnapshot { let text = language::Rope::from(self.text().as_str()); let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::>(); let mut expected_buffer_rows = Vec::new(); - let mut prev_tab_row = 0; + let mut prev_fold_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - if tab_point.row() == prev_tab_row && display_row != 0 { + let suggestion_point = self + .tab_snapshot + .to_suggestion_point(tab_point, Bias::Left) + .0; + let fold_point = self + .tab_snapshot + .suggestion_snapshot + .to_fold_point(suggestion_point); + if fold_point.row() == prev_fold_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let fold_point = self.tab_snapshot.to_fold_point(tab_point, Bias::Left).0; - let buffer_point = fold_point.to_buffer_point(&self.tab_snapshot.fold_snapshot); + let buffer_point = fold_point + .to_buffer_point(&self.tab_snapshot.suggestion_snapshot.fold_snapshot); expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); - prev_tab_row = tab_point.row(); + prev_fold_row = fold_point.row(); } assert_eq!(self.line_len(display_row), text.line_len(display_row)); @@ -1026,7 +1034,7 @@ fn consolidate_wrap_edits(edits: &mut Vec) { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, tab_map::TabMap}, + display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap, tab_map::TabMap}, MultiBuffer, }; use gpui::test::observe; @@ -1076,14 +1084,13 @@ mod tests { } }); let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); - let (mut fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); - log::info!("Unwrapped text (no folds): {:?}", buffer_snapshot.text()); - log::info!( - "Unwrapped text (unexpanded tabs): {:?}", - folds_snapshot.text() - ); - log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text()); + log::info!("Buffer text: {:?}", buffer_snapshot.text()); + let (mut fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + log::info!("FoldMap text: {:?}", fold_snapshot.text()); + let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); + let (tab_map, tabs_snapshot) = TabMap::new(suggestion_snapshot.clone(), tab_size); + log::info!("TabMap text: {:?}", tabs_snapshot.text()); let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system); let unwrapped_text = tabs_snapshot.text(); @@ -1126,9 +1133,11 @@ mod tests { wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); } 20..=39 => { - for (folds_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { + for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(folds_snapshot, fold_edits, tab_size); + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1136,6 +1145,17 @@ mod tests { edits.push((snapshot, wrap_edits)); } } + 40..=59 => { + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.randomly_mutate(&mut rng); + let (tabs_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + let (mut snapshot, wrap_edits) = + wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); + snapshot.check_invariants(); + snapshot.verify_chunks(&mut rng); + edits.push((snapshot, wrap_edits)); + } _ => { buffer.update(cx, |buffer, cx| { let subscription = buffer.subscribe(); @@ -1147,14 +1167,15 @@ mod tests { } } - log::info!("Unwrapped text (no folds): {:?}", buffer_snapshot.text()); - let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - log::info!( - "Unwrapped text (unexpanded tabs): {:?}", - folds_snapshot.text() - ); - let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits, tab_size); - log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text()); + log::info!("Buffer text: {:?}", buffer_snapshot.text()); + let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); + log::info!("FoldMap text: {:?}", fold_snapshot.text()); + let (suggestion_snapshot, suggestion_edits) = + suggestion_map.sync(fold_snapshot, fold_edits); + log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); + let (tabs_snapshot, tab_edits) = + tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); @@ -1201,7 +1222,7 @@ mod tests { if tab_size.get() == 1 || !wrapped_snapshot .tab_snapshot - .fold_snapshot + .suggestion_snapshot .text() .contains('\t') { From ba3913df8c64a40f71a9bfa0ec086263df954345 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 16:41:54 +0100 Subject: [PATCH 18/20] Expose a `DisplayMap::replace_suggestion` method --- crates/editor/src/display_map.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 30388291548d5a36d0da946c3abe9d4fe79d3c69..f49b9e34b8e221a3cf0e79af20c88a472fd7bd4d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -16,6 +16,7 @@ use gpui::{ use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; use settings::Settings; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; +pub use suggestion_map::Suggestion; use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; @@ -229,6 +230,25 @@ impl DisplayMap { self.text_highlights.remove(&Some(type_id)) } + pub fn replace_suggestion( + &self, + new_suggestion: Option>, + cx: &mut ModelContext, + ) where + T: ToPoint, + { + let snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let (snapshot, edits) = self.suggestion_map.replace(new_suggestion, snapshot, edits); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.read(snapshot, edits); + } + pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_font(font_id, font_size, cx)) From f7cba4cec4e21c1642b3e55d2968cf1f09d9203b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 16:51:33 +0100 Subject: [PATCH 19/20] Make `Suggestion` fields public --- crates/editor/src/display_map/suggestion_map.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs index 3e8014724850bccf3c17553a12838a574cf59446..2d0225644fbbb77d8037976c602b92e32792cf02 100644 --- a/crates/editor/src/display_map/suggestion_map.rs +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -58,9 +58,9 @@ impl SuggestionPoint { #[derive(Clone, Debug)] pub struct Suggestion { - position: T, - text: Rope, - highlight_style: HighlightStyle, + pub position: T, + pub text: Rope, + pub highlight_style: HighlightStyle, } pub struct SuggestionMap(Mutex); From 2893c9bdb705a72636f790b251caae3a000b5650 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 21 Mar 2023 17:52:53 +0100 Subject: [PATCH 20/20] Don't move up/down by more rows than the requested ones Co-Authored-By: Max Brunsfeld --- crates/editor/src/movement.rs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 3e5f896564f81a3eea5c3be3152f02a0d7e3b4fe..8685f0709971ac965feac432cbe328c1f2c747fc 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -69,16 +69,11 @@ pub fn up_by_rows( goal_column = 0; } - let clip_bias = if point.column() == map.line_len(point.row()) { - Bias::Left - } else { - Bias::Right - }; - - ( - map.clip_point(point, clip_bias), - SelectionGoal::Column(goal_column), - ) + let mut clipped_point = map.clip_point(point, Bias::Left); + if clipped_point.row() < point.row() { + clipped_point = map.clip_point(point, Bias::Right); + } + (clipped_point, SelectionGoal::Column(goal_column)) } pub fn down_by_rows( @@ -105,16 +100,11 @@ pub fn down_by_rows( goal_column = map.column_to_chars(point.row(), point.column()) } - let clip_bias = if point.column() == map.line_len(point.row()) { - Bias::Left - } else { - Bias::Right - }; - - ( - map.clip_point(point, clip_bias), - SelectionGoal::Column(goal_column), - ) + let mut clipped_point = map.clip_point(point, Bias::Right); + if clipped_point.row() > point.row() { + clipped_point = map.clip_point(point, Bias::Left); + } + (clipped_point, SelectionGoal::Column(goal_column)) } pub fn line_beginning(