From d1f155337f4ea0af8154970a9105f2a73409cfed Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Aug 2022 11:04:59 +0200 Subject: [PATCH 1/4] Recompute layers above tab map entirely when tab size changes Previously, we wouldn't generate any `TabEdit` when the tab size changed, causing coordinate spaces in `WrapMap` and `BlockMap` to become outdated. This commit generates a synthetic edit that covers the entire `TabMap` to ensure layers above are synchronized. --- crates/editor/src/display_map.rs | 33 ++++++++++++++++++++++++ crates/editor/src/display_map/tab_map.rs | 12 +++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 9b12df60d9d1d3c6a67c6f302ab97847ed6c5ae6..ec32cdb3998dfde4488486b880529ef32cd0dea8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1351,6 +1351,39 @@ pub mod tests { ) } + #[gpui::test] + fn test_changing_tab_size(cx: &mut gpui::MutableAppContext) { + cx.set_global({ + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); + settings + }); + let buffer = MultiBuffer::build_simple("\ta\n\tb\n\tc", cx); + let font_cache = cx.font_cache(); + let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); + let font_id = font_cache + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + let map = + cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); + assert_eq!( + map.update(cx, |map, cx| map.snapshot(cx)).text(), + " a\n b\n c" + ); + + cx.set_global({ + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = Some(NonZeroU32::new(2).unwrap()); + settings + }); + assert_eq!( + map.update(cx, |map, cx| map.snapshot(cx)).text(), + " a\n b\n c" + ); + } + fn syntax_chunks<'a>( rows: Range, map: &ModelHandle, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 3ea3f63a575284b7168e564ef42856aa0a5a547c..839edd5c15d771fd75e8064739d7200cfbd658f5 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -27,19 +27,27 @@ impl TabMap { tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); - let max_offset = old_snapshot.fold_snapshot.len(); let new_snapshot = TabSnapshot { fold_snapshot, tab_size, }; + if old_snapshot.tab_size != new_snapshot.tab_size { + let edits = vec![TabEdit { + old: TabPoint::zero()..old_snapshot.max_point(), + new: TabPoint::zero()..new_snapshot.max_point(), + }]; + return (new_snapshot, edits); + } + + let old_max_offset = old_snapshot.fold_snapshot.len(); let mut tab_edits = Vec::with_capacity(fold_edits.len()); for fold_edit in &mut fold_edits { let mut delta = 0; for chunk in old_snapshot .fold_snapshot - .chunks(fold_edit.old.end..max_offset, false, None) + .chunks(fold_edit.old.end..old_max_offset, false, None) { let patterns: &[_] = &['\t', '\n']; if let Some(ix) = chunk.text.find(patterns) { From 385d214b47996a8b38486b7d2485f8c29df03860 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Aug 2022 12:01:26 +0200 Subject: [PATCH 2/4] Enhance `DisplayMap` randomized test to change tab size This removes the need for a unit test. --- crates/editor/src/display_map.rs | 54 +++++++++++--------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ec32cdb3998dfde4488486b880529ef32cd0dea8..93a4c372ec72e32ab1ffa68b14277dc8dfe98109 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -589,7 +589,7 @@ pub mod tests { .unwrap_or(10); let font_cache = cx.font_cache().clone(); - let tab_size = rng.gen_range(1..=4); + let mut tab_size = rng.gen_range(1..=4); let buffer_start_excerpt_header_height = rng.gen_range(1..=5); let excerpt_header_height = rng.gen_range(1..=5); let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); @@ -607,7 +607,11 @@ pub mod tests { log::info!("tab size: {}", tab_size); log::info!("wrap width: {:?}", wrap_width); - cx.update(|cx| cx.set_global(Settings::test(cx))); + cx.update(|cx| { + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); + cx.set_global(settings) + }); let buffer = cx.update(|cx| { if rng.gen() { @@ -653,7 +657,18 @@ pub mod tests { log::info!("setting wrap width to {:?}", wrap_width); map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); } - 20..=44 => { + 20..=29 => { + let mut tab_sizes = vec![1, 2, 3, 4]; + tab_sizes.remove((tab_size - 1) as usize); + tab_size = *tab_sizes.choose(&mut rng).unwrap(); + log::info!("setting tab size to {:?}", tab_size); + cx.update(|cx| { + let mut settings = Settings::test(cx); + settings.editor_overrides.tab_size = NonZeroU32::new(tab_size); + cx.set_global(settings) + }); + } + 30..=44 => { map.update(cx, |map, cx| { if rng.gen() || blocks.is_empty() { let buffer = map.snapshot(cx).buffer_snapshot; @@ -1351,39 +1366,6 @@ pub mod tests { ) } - #[gpui::test] - fn test_changing_tab_size(cx: &mut gpui::MutableAppContext) { - cx.set_global({ - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap()); - settings - }); - let buffer = MultiBuffer::build_simple("\ta\n\tb\n\tc", cx); - let font_cache = cx.font_cache(); - let family_id = font_cache.load_family(&["Helvetica"]).unwrap(); - let font_id = font_cache - .select_font(family_id, &Default::default()) - .unwrap(); - let font_size = 14.0; - - let map = - cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx)); - assert_eq!( - map.update(cx, |map, cx| map.snapshot(cx)).text(), - " a\n b\n c" - ); - - cx.set_global({ - let mut settings = Settings::test(cx); - settings.editor_overrides.tab_size = Some(NonZeroU32::new(2).unwrap()); - settings - }); - assert_eq!( - map.update(cx, |map, cx| map.snapshot(cx)).text(), - " a\n b\n c" - ); - } - fn syntax_chunks<'a>( rows: Range, map: &ModelHandle, From 1e94eb74f48808855c79da2c9c7255772e20cae9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Aug 2022 12:16:46 +0200 Subject: [PATCH 3/4] Don't ignore new tab snapshot in `WrapMap` if only tab size changed --- crates/editor/src/display_map/tab_map.rs | 14 +++++++++----- crates/editor/src/display_map/wrap_map.rs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 839edd5c15d771fd75e8064739d7200cfbd658f5..c3a77cc5789a0d82c69e2a16c2c52b7183885d79 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -16,6 +16,7 @@ impl TabMap { let snapshot = TabSnapshot { fold_snapshot: input, tab_size, + version: 0, }; (Self(Mutex::new(snapshot.clone())), snapshot) } @@ -27,12 +28,14 @@ impl TabMap { tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { let mut old_snapshot = self.0.lock(); - let new_snapshot = TabSnapshot { + let mut new_snapshot = TabSnapshot { fold_snapshot, tab_size, + version: old_snapshot.version, }; if old_snapshot.tab_size != new_snapshot.tab_size { + new_snapshot.version += 1; let edits = vec![TabEdit { old: TabPoint::zero()..old_snapshot.max_point(), new: TabPoint::zero()..new_snapshot.max_point(), @@ -40,6 +43,10 @@ impl TabMap { return (new_snapshot, edits); } + if old_snapshot.fold_snapshot.version != new_snapshot.fold_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()); for fold_edit in &mut fold_edits { @@ -97,6 +104,7 @@ impl TabMap { pub struct TabSnapshot { pub fold_snapshot: FoldSnapshot, pub tab_size: NonZeroU32, + pub version: usize, } impl TabSnapshot { @@ -168,10 +176,6 @@ impl TabSnapshot { } } - pub fn version(&self) -> usize { - self.fold_snapshot.version - } - pub fn chunks<'a>( &'a self, range: Range, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 9584d1c14db55dc29f5cae7b32f77f47dcf9f94d..aa90f942dbfd8a71aec6591c76e9d59506d820ec 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -220,7 +220,7 @@ impl WrapMap { if !self.snapshot.interpolated { let mut to_remove_len = 0; for (tab_snapshot, _) in &self.pending_edits { - if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() { + if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { break; @@ -282,7 +282,7 @@ impl WrapMap { let was_interpolated = self.snapshot.interpolated; let mut to_remove_len = 0; for (tab_snapshot, edits) in &self.pending_edits { - if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() { + if tab_snapshot.version <= self.snapshot.tab_snapshot.version { to_remove_len += 1; } else { let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits); From 3174b0808292a88c325b867e4be9c08eab13ce84 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 3 Aug 2022 12:23:02 +0200 Subject: [PATCH 4/4] Update old snapshot when only the tab size changes --- crates/editor/src/display_map/tab_map.rs | 88 ++++++++++++------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index c3a77cc5789a0d82c69e2a16c2c52b7183885d79..4f718a91398816fd62de8e49be06a48c1c5724c5 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -34,64 +34,64 @@ impl TabMap { version: old_snapshot.version, }; - if old_snapshot.tab_size != new_snapshot.tab_size { - new_snapshot.version += 1; - let edits = vec![TabEdit { - old: TabPoint::zero()..old_snapshot.max_point(), - new: TabPoint::zero()..new_snapshot.max_point(), - }]; - return (new_snapshot, edits); - } - if old_snapshot.fold_snapshot.version != new_snapshot.fold_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()); - for fold_edit in &mut fold_edits { - let mut delta = 0; - for chunk in - old_snapshot - .fold_snapshot - .chunks(fold_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; + + if old_snapshot.tab_size == new_snapshot.tab_size { + for fold_edit in &mut fold_edits { + let mut delta = 0; + for chunk in old_snapshot.fold_snapshot.chunks( + fold_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; + } + + break; } - break; + delta += chunk.text.len(); } - - delta += chunk.text.len(); } - } - let mut ix = 1; - while ix < fold_edits.len() { - let (prev_edits, next_edits) = fold_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); - } else { - ix += 1; + let mut ix = 1; + while ix < fold_edits.len() { + let (prev_edits, next_edits) = fold_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); + } 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 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); + 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), + }); + } + } else { + new_snapshot.version += 1; 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), + old: TabPoint::zero()..old_snapshot.max_point(), + new: TabPoint::zero()..new_snapshot.max_point(), }); }