editor: Fix bracket colorization with folds and large functions (#51108)

Lee ByeongJun and Kirill Bulatov created

Closes #47846

`visible_excerpts` computed the visible buffer range by adding display
line count directly to the buffer start row:

```rust
// Before
multi_buffer_visible_start + Point::new(visible_line_count, 0)
```
This ignores folds entirely. When a 700-line function is folded into one
display line, content after the fold is visible on screen but falls
outside the computed buffer range, so its brackets are never colorized.

The fix converts through display coordinates so the fold/wrap layers are
respected:

```rust
// After
let display_end = DisplayPoint::new(display_start.row + visible_line_count, 0);
let multi_buffer_visible_end = display_end.to_point(&display_snapshot);
```

### Results

**Before Fix**
<img width="852" height="778" alt="스크린샷 2026-03-10 오후 8 29 10"
src="https://github.com/user-attachments/assets/a0d2d81f-a8b2-4cf4-b1f3-cf5f8288a696"
/>

**After Fix**
<img width="1031" height="794" alt="스크린샷 2026-03-10 오후 8 32 27"
src="https://github.com/user-attachments/assets/2b0496b1-8302-4248-b73a-c31f5d0b0c4b"
/>

Before you mark this PR as ready for review, make sure that you have:
- [X] Added a solid test coverage and/or screenshots from doing manual
testing
- [ ] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- Fixed bracket colorization not working for content after folded
regions and for functions with large bodies.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>

Change summary

crates/editor/src/bracket_colorization.rs | 54 +++++++++++++++++++++++++
crates/editor/src/editor.rs               | 24 ++++++-----
2 files changed, 67 insertions(+), 11 deletions(-)

Detailed changes

crates/editor/src/bracket_colorization.rs 🔗

@@ -1455,6 +1455,60 @@ mod foo «1{
         );
     }
 
+    #[gpui::test]
+    // reproduction of #47846
+    async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |language_settings| {
+            language_settings.defaults.colorize_brackets = Some(true);
+        });
+        let mut cx = EditorLspTestContext::new(
+            Arc::into_inner(rust_lang()).unwrap(),
+            lsp::ServerCapabilities::default(),
+            cx,
+        )
+        .await;
+
+        // Generate a large function body. When folded, this collapses
+        // to a single display line, making small_function visible on screen.
+        let mut big_body = String::new();
+        for i in 0..700 {
+            big_body.push_str(&format!("    let var_{i:04} = ({i});\n"));
+        }
+        let source = format!(
+            "ˇfn big_function() {{\n{big_body}}}\n\nfn small_function() {{\n    let x = (1, (2, 3));\n}}\n"
+        );
+
+        cx.set_state(&source);
+        cx.executor().advance_clock(Duration::from_millis(100));
+        cx.executor().run_until_parked();
+
+        cx.update_editor(|editor, window, cx| {
+            editor.fold_ranges(
+                vec![Point::new(0, 0)..Point::new(701, 1)],
+                false,
+                window,
+                cx,
+            );
+        });
+        cx.executor().advance_clock(Duration::from_millis(100));
+        cx.executor().run_until_parked();
+
+        assert_eq!(
+            indoc! {r#"
+⋯1»
+
+fn small_function«1()1» «1{
+    let x = «2(1, «3(2, 3)3»)2»;
+}1»
+
+1 hsla(207.80, 16.20%, 69.19%, 1.00)
+2 hsla(29.00, 54.00%, 65.88%, 1.00)
+3 hsla(286.00, 51.00%, 75.25%, 1.00)
+"#,},
+            bracket_colors_markup(&mut cx),
+        );
+    }
+
     fn separate_with_comment_lines(head: &str, tail: &str, comment_lines: usize) -> String {
         let mut result = head.to_string();
         result.push_str("\n");

crates/editor/src/editor.rs 🔗

@@ -2621,16 +2621,7 @@ impl Editor {
                                 .await;
                             editor
                                 .update_in(cx, |editor, window, cx| {
-                                    editor.register_visible_buffers(cx);
-                                    editor.colorize_brackets(false, cx);
-                                    editor.refresh_inlay_hints(
-                                        InlayHintRefreshReason::NewLinesShown,
-                                        cx,
-                                    );
-                                    if !editor.buffer().read(cx).is_singleton() {
-                                        editor.update_lsp_data(None, window, cx);
-                                        editor.refresh_runnables(window, cx);
-                                    }
+                                    editor.update_data_on_scroll(window, cx)
                                 })
                                 .ok();
                         });
@@ -20055,7 +20046,7 @@ impl Editor {
         &mut self,
         creases: Vec<Crease<T>>,
         auto_scroll: bool,
-        _window: &mut Window,
+        window: &mut Window,
         cx: &mut Context<Self>,
     ) {
         if creases.is_empty() {
@@ -20071,6 +20062,7 @@ impl Editor {
         cx.notify();
 
         self.scrollbar_marker_state.dirty = true;
+        self.update_data_on_scroll(window, cx);
         self.folds_did_change(cx);
     }
 
@@ -25367,6 +25359,16 @@ impl Editor {
     fn disable_runnables(&mut self) {
         self.enable_runnables = false;
     }
+
+    fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
+        self.register_visible_buffers(cx);
+        self.colorize_brackets(false, cx);
+        self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
+        if !self.buffer().read(cx).is_singleton() {
+            self.update_lsp_data(None, window, cx);
+            self.refresh_runnables(window, cx);
+        }
+    }
 }
 
 fn edit_for_markdown_paste<'a>(