diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f8154e6642947ebbe54db2b9fcbfb5973709db19..f49b9e34b8e221a3cf0e79af20c88a472fd7bd4d 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; @@ -15,8 +16,10 @@ 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; +use tab_map::{TabMap, TabSnapshot}; use wrap_map::WrapMap; pub use block_map::{ @@ -24,8 +27,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, @@ -42,6 +43,7 @@ pub struct DisplayMap { buffer: ModelHandle, buffer_subscription: BufferSubscription, fold_map: FoldMap, + suggestion_map: SuggestionMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, @@ -67,6 +69,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); @@ -75,6 +78,7 @@ impl DisplayMap { buffer, buffer_subscription, fold_map, + suggestion_map, tab_map, wrap_map, block_map, @@ -86,21 +90,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, } @@ -124,12 +132,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 @@ -147,12 +157,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 @@ -169,6 +181,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 @@ -186,6 +199,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 @@ -216,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)) @@ -248,10 +281,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, } @@ -259,7 +293,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 { @@ -267,7 +301,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 { @@ -276,9 +310,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; @@ -292,9 +326,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()); @@ -324,28 +358,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) } @@ -353,7 +389,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::>() @@ -363,7 +399,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)) } @@ -371,7 +407,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({ @@ -398,7 +434,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({ @@ -512,7 +548,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 } @@ -523,7 +559,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) } @@ -532,34 +568,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 { @@ -613,18 +649,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 { @@ -741,10 +777,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) } } @@ -867,10 +904,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 { @@ -975,10 +1012,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 new file mode 100644 index 0000000000000000000000000000000000000000..2d0225644fbbb77d8037976c602b92e32792cf02 --- /dev/null +++ b/crates/editor/src/display_map/suggestion_map.rs @@ -0,0 +1,827 @@ +use super::{ + fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, + TextHighlights, +}; +use crate::{MultiBufferSnapshot, ToPoint}; +use gpui::fonts::HighlightStyle; +use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary}; +use parking_lot::Mutex; +use std::{ + cmp, + ops::{Add, AddAssign, Range, Sub}, +}; +use util::post_inc; + +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(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +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 + } + + pub fn column(self) -> u32 { + self.0.column + } +} + +#[derive(Clone, Debug)] +pub struct Suggestion { + pub position: T, + pub text: Rope, + pub highlight_style: HighlightStyle, +} + +pub struct SuggestionMap(Mutex); + +impl SuggestionMap { + pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) { + let snapshot = SuggestionSnapshot { + fold_snapshot, + suggestion: None, + version: 0, + }; + (Self(Mutex::new(snapshot.clone())), snapshot) + } + + pub fn replace( + &self, + new_suggestion: Option>, + fold_snapshot: FoldSnapshot, + fold_edits: Vec, + ) -> (SuggestionSnapshot, Vec) + where + T: ToPoint, + { + 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, + highlight_style: new_suggestion.highlight_style, + } + }); + + let (_, edits) = self.sync(fold_snapshot, fold_edits); + let mut snapshot = self.0.lock(); + + 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), + }]); + } + + 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()), + }]); + } + + snapshot.suggestion = new_suggestion; + snapshot.version += 1; + (snapshot.clone(), patch.into_inner()) + } + + pub fn sync( + &self, + fold_snapshot: FoldSnapshot, + 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; + 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.fold_snapshot = fold_snapshot; + + (snapshot.clone(), suggestion_edits) + } +} + +#[derive(Clone)] +pub struct SuggestionSnapshot { + pub fold_snapshot: FoldSnapshot, + pub 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); + 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 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 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 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 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 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; + let suggestion_end = suggestion_start + suggestion.text.max_point(); + 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 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, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + ) -> SuggestionChunks<'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 + }; + + SuggestionChunks { + prefix_chunks, + suggestion_chunks, + suffix_chunks, + highlight_style: suggestion.highlight_style, + } + } else { + SuggestionChunks { + 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(), + } + } + } + + 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) + .map(|chunk| chunk.text) + .collect() + } +} + +pub struct SuggestionChunks<'a> { + prefix_chunks: Option>, + suggestion_chunks: Option>, + suffix_chunks: Option>, + highlight_style: HighlightStyle, +} + +impl<'a> Iterator for SuggestionChunks<'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 + } +} + +#[derive(Clone)] +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::*; + use crate::{display_map::fold_map::FoldMap, MultiBuffer}; + use gpui::MutableAppContext; + use rand::{prelude::StdRng, Rng}; + use settings::Settings; + use std::{ + env, + ops::{Bound, RangeBounds}, + }; + + #[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)); + 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 mut buffer_snapshot = buffer.read(cx).snapshot(cx); + log::info!("buffer text: {:?}", buffer_snapshot.text()); + + let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); + + 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 => { + let (_, edits) = suggestion_map.randomly_mutate(&mut rng); + 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 (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); + suggestion_edits = suggestion_edits.compose(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); + }), + }; + + 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 = 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 = 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); + end = expected_text.clip_offset(end, Bias::Right); + let mut start = rng.gen_range(0..=end); + start = expected_text.clip_offset(start, Bias::Right); + + let actual_text = suggestion_snapshot + .chunks(SuggestionOffset(start)..SuggestionOffset(end), false, None) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, + expected_text.slice(start..end).to_string(), + "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() { + 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()); + + assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0); + assert_eq!(expected_text.len(), suggestion_snapshot.len().0); + + 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 + ); + assert_eq!( + suggestion_snapshot.to_point(suggestion_offset), + suggestion_point, + "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() { + suggestion_offset.0 += 1; + 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 + ); + } + } + } + } + } + + impl SuggestionMap { + 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 + } 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) + .filter(|ch| *ch != '\r') + .collect::() + .as_str() + .into(), + highlight_style: Default::default(), + }) + }; + + log::info!("replacing suggestion with {:?}", new_suggestion); + self.replace(new_suggestion, fold_snapshot, Default::default()) + } + } +} 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') { 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( 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>,