Add support for skipping brackets in rainbow colorization

MrSubidubi created

Change summary

crates/editor/src/bracket_colorization.rs | 105 ++++++++++++++++--------
crates/language/src/buffer.rs             |  11 +
crates/language/src/language.rs           |   7 +
crates/languages/src/rust/brackets.scm    |   2 
4 files changed, 83 insertions(+), 42 deletions(-)

Detailed changes

crates/editor/src/bracket_colorization.rs 🔗

@@ -63,13 +63,15 @@ impl Editor {
                                 ..multi_buffer_snapshot
                                     .anchor_in_excerpt(excerpt_id, buffer_close_range.end)?;
 
-                            let accent_number = pair.id % accents_count;
-
-                            Some((
-                                accent_number,
-                                multi_buffer_open_range,
-                                multi_buffer_close_range,
-                            ))
+                            pair.id.map(|id| {
+                                let accent_number = id % accents_count;
+
+                                (
+                                    accent_number,
+                                    multi_buffer_open_range,
+                                    multi_buffer_close_range,
+                                )
+                            })
                         });
 
                     for (accent_number, open_range, close_range) in brackets_by_accent {
@@ -109,16 +111,40 @@ impl Editor {
 
 #[cfg(test)]
 mod tests {
-    use std::time::Duration;
+    use std::{ops::Range, time::Duration};
 
     use super::*;
-    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
+    use crate::{
+        editor_tests::init_test,
+        test::{
+            editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
+        },
+    };
+    use gpui::Hsla;
     use indoc::indoc;
     use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
     use multi_buffer::AnchorRangeExt as _;
+    use rope::Point;
 
     #[gpui::test]
     async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
+        fn collect_colored_brackets(
+            cx: &mut EditorTestContext,
+        ) -> Vec<(Option<Hsla>, Range<Point>)> {
+            cx.update_editor(|editor, window, cx| {
+                let snapshot = editor.snapshot(window, cx);
+                snapshot
+                    .all_text_highlight_ranges::<RainbowBracketHighlight>()
+                    .iter()
+                    .flat_map(|ranges| {
+                        ranges.1.iter().map(|range| {
+                            (ranges.0.color, range.to_point(&snapshot.buffer_snapshot()))
+                        })
+                    })
+                    .collect::<Vec<_>>()
+            })
+        }
+
         init_test(cx, |language_settings| {
             language_settings.defaults.colorize_brackets = Some(true);
         });
@@ -164,6 +190,8 @@ mod tests {
         )
         .await;
 
+        let mut highlighted_brackets = HashMap::default();
+
         // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
         cx.set_state(indoc! {r#"ˇ
             pub(crate) fn inlay_hints(
@@ -387,19 +415,12 @@ mod tests {
         cx.executor().advance_clock(Duration::from_millis(100));
         cx.executor().run_until_parked();
 
-        let actual_ranges = cx.update_editor(|editor, window, cx| {
-            let snapshot = editor.snapshot(window, cx);
-            snapshot
-                .all_text_highlight_ranges::<RainbowBracketHighlight>()
-                .iter()
-                .flat_map(|ranges| {
-                    ranges
-                        .1
-                        .iter()
-                        .map(|range| (ranges.0.color, range.to_point(&snapshot.buffer_snapshot())))
-                })
-                .collect::<Vec<_>>()
-        });
+        let actual_ranges = collect_colored_brackets(&mut cx);
+
+        for (color, range) in actual_ranges.iter().cloned() {
+            highlighted_brackets.insert(range, color);
+        }
+
         let last_bracket = actual_ranges
             .iter()
             .max_by_key(|(_, p)| p.end.row)
@@ -417,28 +438,40 @@ mod tests {
         cx.executor().advance_clock(Duration::from_millis(100));
         cx.executor().run_until_parked();
 
-        let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
-            let snapshot = editor.snapshot(window, cx);
-            snapshot
-                .all_text_highlight_ranges::<RainbowBracketHighlight>()
-                .iter()
-                .flat_map(|ranges| {
-                    ranges
-                        .1
-                        .iter()
-                        .map(|range| (ranges.0.color, range.to_point(&snapshot.buffer_snapshot())))
-                })
-                .collect::<Vec<_>>()
-        });
+        let ranges_after_scrolling = collect_colored_brackets(&mut cx);
         let new_last_bracket = ranges_after_scrolling
             .iter()
             .max_by_key(|(_, p)| p.end.row)
             .unwrap()
             .clone();
-        // todo! more tests, check consistency of the colors picked also, settings toggle
+
         assert_ne!(
             last_bracket, new_last_bracket,
             "After scrolling down, we should have highlighted more brackets"
         );
+
+        cx.update_editor(|editor, window, cx| {
+            let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
+            assert!(was_scrolled.0);
+        });
+
+        for _ in 0..200 {
+            cx.update_editor(|editor, window, cx| {
+                editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
+            });
+            cx.executor().run_until_parked();
+
+            for (color, range) in collect_colored_brackets(&mut cx) {
+                assert!(
+                    highlighted_brackets
+                        .entry(range.clone())
+                        .or_insert(color.clone())
+                        == &color,
+                    "Colors should stay consistent while scrolling!"
+                );
+            }
+        }
+
+        // todo! more tests, check no brackets missing in range, settings toggle
     }
 }

crates/language/src/buffer.rs 🔗

@@ -48,7 +48,7 @@ use std::{
     iter::{self, Iterator, Peekable},
     mem,
     num::NonZeroU32,
-    ops::{Deref, Range},
+    ops::{Deref, Not, Range},
     path::PathBuf,
     rc,
     sync::{Arc, LazyLock},
@@ -850,7 +850,7 @@ pub struct BracketMatch {
     pub open_range: Range<usize>,
     pub close_range: Range<usize>,
     pub newline_only: bool,
-    pub id: usize,
+    pub id: Option<usize>,
 }
 
 impl BracketMatch {
@@ -4210,7 +4210,7 @@ impl BufferSnapshot {
                             .as_ref()
                             .and_then(|previous_brackets| previous_brackets.last())
                             // Try to continue previous sequence of IDs.
-                            .map(|bracket| bracket.id + 1)
+                            .and_then(|bracket| bracket.id.map(|id| id + 1))
                             // If not possible, start another sequence: pick it far enough to avoid overlaps.
                             //
                             // This for sure will introduce the gaps between chunks' bracket IDs,
@@ -4261,7 +4261,10 @@ impl BufferSnapshot {
                                 open_range,
                                 close_range,
                                 newline_only: pattern.newline_only,
-                                id: post_inc(&mut next_id),
+                                id: pattern
+                                    .rainbow_exclude
+                                    .not()
+                                    .then(|| post_inc(&mut next_id)),
                             });
                         }
                         None

crates/language/src/language.rs 🔗

@@ -1322,6 +1322,7 @@ struct BracketsConfig {
 #[derive(Clone, Debug, Default)]
 struct BracketsPatternConfig {
     newline_only: bool,
+    rainbow_exclude: bool,
 }
 
 pub struct DebugVariablesConfig {
@@ -1684,9 +1685,13 @@ impl Language {
                 .map(|ix| {
                     let mut config = BracketsPatternConfig::default();
                     for setting in query.property_settings(ix) {
-                        if setting.key.as_ref() == "newline.only" {
+                        let setting_key = setting.key.as_ref();
+                        if setting_key == "newline.only" {
                             config.newline_only = true
                         }
+                        if setting_key == "rainbow.exclude" {
+                            config.rainbow_exclude = true
+                        }
                     }
                     config
                 })

crates/languages/src/rust/brackets.scm 🔗

@@ -2,6 +2,6 @@
 ("[" @open "]" @close)
 ("{" @open "}" @close)
 ("<" @open ">" @close)
-("\"" @open "\"" @close)
+(("\"" @open "\"" @close) (#set! rainbow.exclude))
 (closure_parameters "|" @open "|" @close)
 ("'" @open "'" @close)