Recompute layers above tab map entirely when tab size changes

Antonio Scandurra created

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.

Change summary

crates/editor/src/display_map.rs         | 33 ++++++++++++++++++++++++++
crates/editor/src/display_map/tab_map.rs | 12 +++++++-
2 files changed, 43 insertions(+), 2 deletions(-)

Detailed changes

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<u32>,
         map: &ModelHandle<DisplayMap>,

crates/editor/src/display_map/tab_map.rs 🔗

@@ -27,19 +27,27 @@ impl TabMap {
         tab_size: NonZeroU32,
     ) -> (TabSnapshot, Vec<TabEdit>) {
         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) {