Skip to content

Basic Rainbow Brackets#1

Merged
jsfillman merged 8 commits intofeature/rainbow_brackets_plusfrom
royal_rainbow
Oct 10, 2025
Merged

Basic Rainbow Brackets#1
jsfillman merged 8 commits intofeature/rainbow_brackets_plusfrom
royal_rainbow

Conversation

@jsfillman
Copy link
Owner

@jsfillman jsfillman commented Oct 9, 2025

User description

Closes zed-industries#5259

Release Notes:

PR Type

Enhancement


Description

  • Add rainbow bracket coloring feature for nested code blocks

  • Include performance optimizations with caching and bracket limits


Diagram Walkthrough

flowchart LR
  A["Editor Settings"] --> B["Rainbow Bracket Tracker"]
  B --> C["Tree-sitter Parser"]
  C --> D["Bracket Detection"]
  D --> E["Nesting Level Calculation"]
  E --> F["Color Assignment"]
  F --> G["Highlight Rendering"]
  B --> H["Animation System"]
  H --> G
Loading

File Walkthrough

Relevant files
Enhancement
2 files
editor.rs
Integrate rainbow brackets into editor core                           
+8/-0     
rainbow_brackets.rs
Implement rainbow bracket tracking and coloring system     
+1059/-0
Configuration changes
3 files
editor_settings.rs
Add rainbow bracket configuration settings                             
+62/-0   
editor.rs
Define rainbow bracket settings schema                                     
+88/-0   
default.json
Add default rainbow bracket configuration                               
+32/-0   
Miscellaneous
1 files
CLAUDE.md
Remove file reference                                                                       
+0/-1     
Dependencies
1 files
Cargo.toml
Add indexmap dependency for ordered collections                   
+1/-0     
Documentation
2 files
SUMMARY.md
Add rainbow brackets documentation link                                   
+1/-0     
rainbow_brackets.md
Create comprehensive rainbow brackets documentation           
+149/-0 

@jsfillman jsfillman self-assigned this Oct 9, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch royal_rainbow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 9, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 9, 2025

PR Code Suggestions ✨

Latest suggestions up to f679e53

CategorySuggestion                                                                                                                                    Impact
Possible issue
Remove unsupported settings keys
Suggestion Impact:The commit removes the legacy keys (mode, gradient_start_hue, gradient_step, and various animation/minimap settings) and replaces them with the simplified keys start_hue, hue_step, and max_brackets, aligning with the suggestion.

code diff:

   // Rainbow brackets settings
   "rainbow_brackets": {
     // Whether rainbow brackets are enabled.
-    // When enabled, brackets are colored by their nesting level using a gradient or fixed colors.
+    // When enabled, brackets are colored by their nesting level.
     "enabled": true,
-    // The coloring mode for rainbow brackets.
-    // - "gradient": Infinite HSL gradient with no color repetition (Zed's innovation)
-    // - "classic": 6-color cycling for VS Code parity
-    "mode": "gradient",
-    // Starting hue for gradient mode (0-360 degrees).
+    // Starting hue for bracket colors (0-360 degrees).
     // 0 = red, 60 = yellow, 120 = green, 180 = cyan, 240 = blue, 300 = magenta
-    "gradient_start_hue": 0,
-    // Hue step per nesting level for gradient mode (degrees).
-    // 60 degrees gives 6 highly contrasting colors: red, yellow, green, cyan, blue, magenta.
-    "gradient_step": 60,
-    // Whether to show bracket structure visualization in the minimap.
-    "show_in_minimap": true,
-    // Whether to pulse/animate the active bracket pair containing the cursor.
-    "pulse_active_scope": true,
-    // Duration of the pulse animation in milliseconds.
-    "pulse_duration_ms": 300,
-    // Whether to dim inactive bracket scopes for better focus.
-    "dim_inactive_scopes": false,
-    // Whether to show cascade fade-in animation when opening files or scrolling.
-    // Brackets fade in smoothly, staggered by depth for a cascading effect.
-    "animate_fade": false,
-    // Whether to show subtle glow animation on the active bracket pair.
-    // The active brackets pulse gently to draw attention.
-    "animate_glow": false,
-    // Duration of fade-in animation in milliseconds.
-    "animation_duration_ms": 200
+    "start_hue": 0.0,
+    // Hue step per nesting level (degrees).
+    // 30 degrees provides good visual distinction between levels.
+    "hue_step": 30.0,
+    // Maximum number of brackets to color (performance limit).
+    "max_brackets": 1000
   },

Remove unsupported legacy keys from the rainbow_brackets section in default.json
to align with the new, simplified implementation.

assets/settings/default.json [580-610]

 "rainbow_brackets": {
   // Whether rainbow brackets are enabled.
-  // When enabled, brackets are colored by their nesting level using a gradient or fixed colors.
   "enabled": true,
-  // The coloring mode for rainbow brackets.
-  // - "gradient": Infinite HSL gradient with no color repetition (Zed's innovation)
-  // - "classic": 6-color cycling for VS Code parity
-  "mode": "gradient",
-  // Starting hue for gradient mode (0-360 degrees).
-  // 0 = red, 60 = yellow, 120 = green, 180 = cyan, 240 = blue, 300 = magenta
-  "gradient_start_hue": 0,
-  // Hue step per nesting level for gradient mode (degrees).
-  // 60 degrees gives 6 highly contrasting colors: red, yellow, green, cyan, blue, magenta.
-  "gradient_step": 60,
-  // Whether to show bracket structure visualization in the minimap.
-  "show_in_minimap": true,
-  // Whether to pulse/animate the active bracket pair containing the cursor.
-  "pulse_active_scope": true,
-  // Duration of the pulse animation in milliseconds.
-  "pulse_duration_ms": 300,
-  // Whether to dim inactive bracket scopes for better focus.
-  "dim_inactive_scopes": false,
-  // Whether to show cascade fade-in animation when opening files or scrolling.
-  // Brackets fade in smoothly, staggered by depth for a cascading effect.
-  "animate_fade": false,
-  // Whether to show subtle glow animation on the active bracket pair.
-  // The active brackets pulse gently to draw attention.
-  "animate_glow": false,
-  // Duration of fade-in animation in milliseconds.
-  "animation_duration_ms": 200
+  // Starting hue (0-360). 0=red, 120=green, 240=blue.
+  "start_hue": 0,
+  // Hue step per nesting level (degrees).
+  "hue_step": 60,
+  // Maximum number of brackets to colorize for performance.
+  "max_brackets": 100000
 },

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the PR adds unsupported legacy settings to default.json, which contradicts the refactoring's goal of simplifying configuration and will confuse users.

Medium
General
Window large-file processing around cursor

For large files, process a 50k character window centered around the cursor
instead of the first 50k characters of the file.

crates/editor/src/rainbow_brackets.rs [324-333]

-// For large files, limit processing to avoid performance issues
+// For large files, limit processing to a window around the cursor to avoid off-screen highlights
 let buffer_len = buffer.len();
 let visible_buffer_range = if buffer_len > 100_000 {
-    // For very large files (>100K chars), process only a limited range
-    // This prevents performance issues while still providing some functionality
-    Some(0..50_000)
+    // Center a processing window around the newest cursor head
+    let cursor_offset = editor.selections.newest_anchor().head().to_offset(&buffer);
+    let half_window: usize = 25_000;
+    let start = cursor_offset.saturating_sub(half_window);
+    let end = (cursor_offset + half_window).min(buffer_len);
+    Some(start..end)
 } else {
     // For normal-sized files, process everything
     None
 };
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion offers a good heuristic to improve user experience on very large files by processing a range around the cursor instead of from the start of the file, making the feature more useful in that edge case.

Medium
  • Update

Previous suggestions

✅ Suggestions up to commit 33128c0
CategorySuggestion                                                                                                                                    Impact
Possible issue
Align default settings with JSON
Suggestion Impact:The commit modified the RainbowBracketSettings structure and initialization, removing several fields (mode, animations, etc.) and focusing on start_hue/hue_step and max_brackets, thereby altering defaults and simplifying logic similar to the suggestion’s aim of aligning defaults and reducing redundancy. Although not identical to the proposed code, it reflects an alignment/refactor impacting the same section.

code diff:

 /// Rainbow brackets settings
 #[derive(Clone, Debug, PartialEq)]
 pub struct RainbowBracketSettings {
     pub enabled: bool,
-    pub mode: RainbowMode,
-    pub gradient_start_hue: f32,
-    pub gradient_step: f32,
-    pub show_in_minimap: bool,
-    pub pulse_active_scope: bool,
-    pub pulse_duration_ms: u32,
-    pub dim_inactive_scopes: bool,
-    pub animate_fade: bool,
-    pub animate_glow: bool,
-    pub animation_duration_ms: u32,
+    pub start_hue: f32,
+    pub hue_step: f32,
     pub max_brackets: u32,
 }
 
@@ -297,35 +282,16 @@
                 if let Some(rb) = editor.rainbow_brackets {
                     RainbowBracketSettings {
                         enabled: rb.enabled.unwrap_or(true),
-                        mode: match rb.mode.unwrap_or(settings::RainbowModeContent::Gradient) {
-                            settings::RainbowModeContent::Gradient => RainbowMode::Gradient,
-                            settings::RainbowModeContent::Classic => RainbowMode::Classic,
-                        },
-                        gradient_start_hue: rb.gradient_start_hue.unwrap_or(0.0),
-                        gradient_step: rb.gradient_step.unwrap_or(30.0),
-                        show_in_minimap: rb.show_in_minimap.unwrap_or(true),
-                        pulse_active_scope: rb.pulse_active_scope.unwrap_or(true),
-                        pulse_duration_ms: rb.pulse_duration_ms.unwrap_or(300),
-                        dim_inactive_scopes: rb.dim_inactive_scopes.unwrap_or(false),
-                        animate_fade: rb.animate_fade.unwrap_or(true),
-                        animate_glow: rb.animate_glow.unwrap_or(true),
-                        animation_duration_ms: rb.animation_duration_ms.unwrap_or(200),
+                        start_hue: rb.start_hue.unwrap_or(0.0),
+                        hue_step: rb.hue_step.unwrap_or(30.0),
                         max_brackets: rb.max_brackets.unwrap_or(100000),
                     }
                 } else {
                     // Default values when rainbow_brackets is not specified
                     RainbowBracketSettings {
                         enabled: true,
-                        mode: RainbowMode::Gradient,
-                        gradient_start_hue: 0.0,
-                        gradient_step: 30.0,
-                        show_in_minimap: true,
-                        pulse_active_scope: true,
-                        pulse_duration_ms: 300,
-                        dim_inactive_scopes: false,
-                        animate_fade: true,
-                        animate_glow: true,
-                        animation_duration_ms: 200,
+                        start_hue: 0.0,
+                        hue_step: 30.0,
                         max_brackets: 100000,
                     }

Align the default values for rainbow_brackets in the Rust code with those in
default.json to ensure consistent behavior and refactor the code to remove
redundancy.

crates/editor/src/editor_settings.rs [297-332]

-if let Some(rb) = editor.rainbow_brackets {
-    RainbowBracketSettings {
-        enabled: rb.enabled.unwrap_or(true),
-        mode: match rb.mode.unwrap_or(settings::RainbowModeContent::Gradient) {
-            settings::RainbowModeContent::Gradient => RainbowMode::Gradient,
-            settings::RainbowModeContent::Classic => RainbowMode::Classic,
-        },
-        gradient_start_hue: rb.gradient_start_hue.unwrap_or(0.0),
-        gradient_step: rb.gradient_step.unwrap_or(30.0),
-        show_in_minimap: rb.show_in_minimap.unwrap_or(true),
-        pulse_active_scope: rb.pulse_active_scope.unwrap_or(true),
-        pulse_duration_ms: rb.pulse_duration_ms.unwrap_or(300),
-        dim_inactive_scopes: rb.dim_inactive_scopes.unwrap_or(false),
-        animate_fade: rb.animate_fade.unwrap_or(true),
-        animate_glow: rb.animate_glow.unwrap_or(true),
-        animation_duration_ms: rb.animation_duration_ms.unwrap_or(200),
-        max_brackets: rb.max_brackets.unwrap_or(100000),
-    }
-} else {
-    // Default values when rainbow_brackets is not specified
-    RainbowBracketSettings {
-        enabled: true,
-        mode: RainbowMode::Gradient,
-        gradient_start_hue: 0.0,
-        gradient_step: 30.0,
-        show_in_minimap: true,
-        pulse_active_scope: true,
-        pulse_duration_ms: 300,
-        dim_inactive_scopes: false,
-        animate_fade: true,
-        animate_glow: true,
-        animation_duration_ms: 200,
-        max_brackets: 100000,
-    }
+let rb = editor.rainbow_brackets.as_ref();
+RainbowBracketSettings {
+    enabled: rb.and_then(|s| s.enabled).unwrap_or(true),
+    mode: match rb
+        .and_then(|s| s.mode)
+        .unwrap_or(settings::RainbowModeContent::Gradient)
+    {
+        settings::RainbowModeContent::Gradient => RainbowMode::Gradient,
+        settings::RainbowModeContent::Classic => RainbowMode::Classic,
+    },
+    gradient_start_hue: rb.and_then(|s| s.gradient_start_hue).unwrap_or(0.0),
+    gradient_step: rb.and_then(|s| s.gradient_step).unwrap_or(60.0),
+    show_in_minimap: rb.and_then(|s| s.show_in_minimap).unwrap_or(true),
+    pulse_active_scope: rb.and_then(|s| s.pulse_active_scope).unwrap_or(true),
+    pulse_duration_ms: rb.and_then(|s| s.pulse_duration_ms).unwrap_or(300),
+    dim_inactive_scopes: rb.and_then(|s| s.dim_inactive_scopes).unwrap_or(false),
+    animate_fade: rb.and_then(|s| s.animate_fade).unwrap_or(false),
+    animate_glow: rb.and_then(|s| s.animate_glow).unwrap_or(false),
+    animation_duration_ms: rb.and_then(|s| s.animation_duration_ms).unwrap_or(200),
+    max_brackets: rb.and_then(|s| s.max_brackets).unwrap_or(100000),
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies and fixes an inconsistency between default settings in the JSON file and the Rust code, which improves correctness and user experience.

Low
Avoid potential panic from indexing
Suggestion Impact:The macro was refactored to avoid indexing into a vector of tuples; the code now stores only ranges per level and computes the color separately, eliminating the need to access by_level[...][0] and thus removing the potential panic path. This aligns with the intent of safer access.

code diff:

+        by_level[color_index].push(range.clone());
+    }
+
+    // Apply highlights using different type for each level
     macro_rules! apply_level {
         ($index:literal, $type:ty) => {
             if !by_level[$index].is_empty() {
-                let ranges: Vec<_> = by_level[$index].iter().map(|(r, _)| r.clone()).collect();
-                let mut color = by_level[$index][0].1; // All brackets at this level have same color
-                // If fade is active, apply a uniform alpha for this frame
-                if let Some(alpha) = editor.rainbow_bracket_tracker.calculate_fade_progress() {
-                    color = hsla(color.h, color.s, color.l, alpha);
-                }
+                let level = $index as u32;
+                let color = editor.rainbow_bracket_tracker.get_color_for_level(level);
                 editor.highlight_text::<$type>(
-                    ranges,
+                    by_level[$index].clone(),
                     HighlightStyle {
                         color: Some(color),
                         ..Default::default()
                     },
-                    cx
+                    cx,
                 );
             }
         };

Refactor the apply_level! macro to use get(0) and if let for safer access to the
first element of the by_level vector, avoiding a potential panic.

crates/editor/src/rainbow_brackets.rs [550-569]

 macro_rules! apply_level {
     ($index:literal, $type:ty) => {
-        if !by_level[$index].is_empty() {
+        if let Some((_, color)) = by_level[$index].get(0) {
             let ranges: Vec<_> = by_level[$index].iter().map(|(r, _)| r.clone()).collect();
-            let mut color = by_level[$index][0].1; // All brackets at this level have same color
+            let mut color = *color; // All brackets at this level have same color
             // If fade is active, apply a uniform alpha for this frame
             if let Some(alpha) = editor.rainbow_bracket_tracker.calculate_fade_progress() {
                 color = hsla(color.h, color.s, color.l, alpha);
             }
             editor.highlight_text::<$type>(
                 ranges,
                 HighlightStyle {
                     color: Some(color),
                     ..Default::default()
                 },
                 cx
             );
         }
     };
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion proposes a safer, more idiomatic way to access the first element of a vector, making the code more robust and readable, even though the existing code is not buggy.

Low
General
Avoid intermediate collection to save memory
Suggestion Impact:The commit removed the intermediate pairs_vec collection and iterated over pairs directly when pushing brackets, aligning with the suggestion to avoid unnecessary allocation. The related trace logging of counts tied to pairs_vec was also removed/adjusted.

code diff:

 
-            let pairs_vec: Vec<_> = pairs.collect();
-            log::trace!(
-                "rainbow_brackets: tree-sitter pairs found count={} buffer_end={}",
-                pairs_vec.len(),
-                entire_buffer_range.end
-            );
-
-            for (open_range, close_range) in pairs_vec {
+            for (open_range, close_range) in pairs {
                 brackets.push((open_range.start, true, open_range));
                 brackets.push((close_range.start, false, close_range));
             }
 
-            // Sort by position so we process brackets in document order
+            // Sort by position to process in document order
             brackets.sort_by_key(|(pos, _, _)| *pos);

Avoid an unnecessary intermediate collection of bracket pairs to reduce memory
usage by processing the iterator directly.

crates/editor/src/rainbow_brackets.rs [189-199]

-let pairs_vec: Vec<_> = pairs.collect();
+for (open_range, close_range) in pairs {
+    brackets.push((open_range.start, true, open_range));
+    brackets.push((close_range.start, false, close_range));
+}
 log::trace!(
-    "rainbow_brackets: tree-sitter pairs found count={} buffer_end={}",
-    pairs_vec.len(),
+    "rainbow_brackets: tree-sitter pairs processed count={} buffer_end={}",
+    brackets.len() / 2,
     entire_buffer_range.end
 );
 
-for (open_range, close_range) in pairs_vec {
-    brackets.push((open_range.start, true, open_range));
-    brackets.push((close_range.start, false, close_range));
-}
-

[Suggestion processed]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out an unnecessary intermediate collection, and removing it improves memory efficiency, which is a good optimization for large files.

Low
General
Preserve cache across toggles

Preserve cached_edit_count when the rainbow brackets feature is disabled to
avoid unnecessary re-computation when it is re-enabled.

crates/editor/src/rainbow_brackets.rs [142-147]

 if !self.enabled {
     self.nesting_levels.clear();
     self.last_viewport = None;
-    self.cached_edit_count = None;
+    // Preserve cached_edit_count so we can reuse it when re-enabled
     return;
 }
Suggestion importance[1-10]: 7

__

Why: This is a valid optimization that improves the user experience when toggling the feature by preserving the cache, thus avoiding unnecessary re-computation if the buffer is unchanged.

Medium

@jsfillman jsfillman changed the base branch from feature/rainbow_brackets_plus to main October 9, 2025 11:19
@jsfillman jsfillman changed the title @coderabbit Basic Rainbow Brackets Oct 9, 2025
@aurexav
Copy link

aurexav commented Oct 9, 2025

Submit to wrong repo?

@jsfillman jsfillman changed the base branch from main to feature/rainbow_brackets_plus October 9, 2025 23:59
@jsfillman jsfillman merged commit 5e9d020 into feature/rainbow_brackets_plus Oct 10, 2025
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rainbow brackets

2 participants