bracket_colorization.rs

   1//! Bracket highlights, also known as "rainbow brackets".
   2//! Uses tree-sitter queries from brackets.scm to capture bracket pairs,
   3//! and theme accents to colorize those.
   4
   5use std::ops::Range;
   6
   7use crate::{Editor, HighlightKey};
   8use collections::{HashMap, HashSet};
   9use gpui::{AppContext as _, Context, HighlightStyle};
  10use language::{BufferRow, BufferSnapshot, language_settings::LanguageSettings};
  11use multi_buffer::{Anchor, BufferOffset, ExcerptRange, MultiBufferSnapshot};
  12use text::OffsetRangeExt as _;
  13use ui::{ActiveTheme, utils::ensure_minimum_contrast};
  14
  15impl Editor {
  16    pub(crate) fn colorize_brackets(&mut self, invalidate: bool, cx: &mut Context<Editor>) {
  17        if !self.mode.is_full() {
  18            return;
  19        }
  20
  21        if invalidate {
  22            self.bracket_fetched_tree_sitter_chunks.clear();
  23        }
  24
  25        let accents_count = cx.theme().accents().0.len();
  26        let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
  27
  28        let visible_excerpts = self.visible_buffer_ranges(cx);
  29        let excerpt_data: Vec<(
  30            BufferSnapshot,
  31            Range<BufferOffset>,
  32            ExcerptRange<text::Anchor>,
  33        )> = visible_excerpts
  34            .into_iter()
  35            .filter(|(buffer_snapshot, _, _)| {
  36                let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id())
  37                else {
  38                    return false;
  39                };
  40                LanguageSettings::for_buffer(buffer.read(cx), cx).colorize_brackets
  41            })
  42            .collect();
  43
  44        let mut fetched_tree_sitter_chunks = excerpt_data
  45            .iter()
  46            .filter_map(|(_, _, excerpt_range)| {
  47                let key = excerpt_range.context.clone();
  48                Some((
  49                    key.clone(),
  50                    self.bracket_fetched_tree_sitter_chunks.get(&key).cloned()?,
  51                ))
  52            })
  53            .collect::<HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>>();
  54
  55        let bracket_matches_by_accent = cx.background_spawn(async move {
  56            let bracket_matches_by_accent: HashMap<usize, Vec<Range<Anchor>>> =
  57                excerpt_data.into_iter().fold(
  58                    HashMap::default(),
  59                    |mut acc, (buffer_snapshot, buffer_range, excerpt_range)| {
  60                        let fetched_chunks = fetched_tree_sitter_chunks
  61                            .entry(excerpt_range.context.clone())
  62                            .or_default();
  63
  64                        let brackets_by_accent = compute_bracket_ranges(
  65                            &multi_buffer_snapshot,
  66                            &buffer_snapshot,
  67                            buffer_range,
  68                            excerpt_range,
  69                            fetched_chunks,
  70                            accents_count,
  71                        );
  72
  73                        for (accent_number, new_ranges) in brackets_by_accent {
  74                            let ranges = acc
  75                                .entry(accent_number)
  76                                .or_insert_with(Vec::<Range<Anchor>>::new);
  77
  78                            for new_range in new_ranges {
  79                                let i = ranges
  80                                    .binary_search_by(|probe| {
  81                                        probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
  82                                    })
  83                                    .unwrap_or_else(|i| i);
  84                                ranges.insert(i, new_range);
  85                            }
  86                        }
  87
  88                        acc
  89                    },
  90                );
  91
  92            (bracket_matches_by_accent, fetched_tree_sitter_chunks)
  93        });
  94
  95        let editor_background = cx.theme().colors().editor_background;
  96        let accents = cx.theme().accents().clone();
  97
  98        self.colorize_brackets_task = cx.spawn(async move |editor, cx| {
  99            if invalidate {
 100                editor
 101                    .update(cx, |editor, cx| {
 102                        editor.clear_highlights_with(
 103                            &mut |key| matches!(key, HighlightKey::ColorizeBracket(_)),
 104                            cx,
 105                        );
 106                    })
 107                    .ok();
 108            }
 109
 110            let (bracket_matches_by_accent, updated_chunks) = bracket_matches_by_accent.await;
 111
 112            editor
 113                .update(cx, |editor, cx| {
 114                    editor
 115                        .bracket_fetched_tree_sitter_chunks
 116                        .extend(updated_chunks);
 117                    for (accent_number, bracket_highlights) in bracket_matches_by_accent {
 118                        let bracket_color = accents.color_for_index(accent_number as u32);
 119                        let adjusted_color =
 120                            ensure_minimum_contrast(bracket_color, editor_background, 55.0);
 121                        let style = HighlightStyle {
 122                            color: Some(adjusted_color),
 123                            ..HighlightStyle::default()
 124                        };
 125
 126                        editor.highlight_text_key(
 127                            HighlightKey::ColorizeBracket(accent_number),
 128                            bracket_highlights,
 129                            style,
 130                            true,
 131                            cx,
 132                        );
 133                    }
 134                })
 135                .ok();
 136        });
 137    }
 138}
 139
 140fn compute_bracket_ranges(
 141    multi_buffer_snapshot: &MultiBufferSnapshot,
 142    buffer_snapshot: &BufferSnapshot,
 143    buffer_range: Range<BufferOffset>,
 144    excerpt_range: ExcerptRange<text::Anchor>,
 145    fetched_chunks: &mut HashSet<Range<BufferRow>>,
 146    accents_count: usize,
 147) -> Vec<(usize, Vec<Range<Anchor>>)> {
 148    let context = excerpt_range.context.to_offset(buffer_snapshot);
 149    let buffer_range = buffer_range.start.0..buffer_range.end.0;
 150    let large_block_pairs = buffer_snapshot.bracket_pairs_for_large_enclosing_blocks(&buffer_range);
 151    let large_block_depth = large_block_pairs.len();
 152
 153    buffer_snapshot
 154        .fetch_bracket_ranges(buffer_range, Some(fetched_chunks))
 155        .into_iter()
 156        .flat_map(|(chunk_range, pairs)| {
 157            if fetched_chunks.insert(chunk_range) {
 158                pairs
 159            } else {
 160                Vec::new()
 161            }
 162        })
 163        .map(move |mut pair| {
 164            if let Some(idx) = pair.color_index.as_mut() {
 165                *idx += large_block_depth;
 166            }
 167            pair
 168        })
 169        .chain(large_block_pairs)
 170        .filter_map(|pair| {
 171            let color_index = pair.color_index?;
 172
 173            let mut ranges = Vec::new();
 174
 175            if context.start <= pair.open_range.start && pair.open_range.end <= context.end {
 176                let anchors = buffer_snapshot.anchor_range_inside(pair.open_range);
 177                ranges.push(
 178                    multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
 179                        ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
 180                );
 181            };
 182
 183            if context.start <= pair.close_range.start && pair.close_range.end <= context.end {
 184                let anchors = buffer_snapshot.anchor_range_inside(pair.close_range);
 185                ranges.push(
 186                    multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
 187                        ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
 188                );
 189            };
 190
 191            Some((color_index % accents_count, ranges))
 192        })
 193        .collect()
 194}
 195
 196#[cfg(test)]
 197mod tests {
 198    use std::{cmp, sync::Arc, time::Duration};
 199
 200    use super::*;
 201    use crate::{
 202        DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
 203        display_map::{DisplayRow, ToDisplayPoint},
 204        editor_tests::init_test,
 205        test::{
 206            editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
 207        },
 208    };
 209    use collections::HashSet;
 210    use fs::FakeFs;
 211    use gpui::UpdateGlobal as _;
 212    use indoc::indoc;
 213    use itertools::Itertools;
 214    use language::{Capability, markdown_lang};
 215    use languages::rust_lang;
 216    use multi_buffer::{MultiBuffer, PathKey};
 217    use pretty_assertions::assert_eq;
 218    use project::Project;
 219    use rope::Point;
 220    use serde_json::json;
 221    use settings::{AccentContent, SettingsStore};
 222    use text::{Bias, OffsetRangeExt, ToOffset};
 223    use theme_settings::ThemeStyleContent;
 224
 225    use util::{path, post_inc};
 226
 227    #[gpui::test]
 228    async fn test_basic_bracket_colorization(cx: &mut gpui::TestAppContext) {
 229        init_test(cx, |language_settings| {
 230            language_settings.defaults.colorize_brackets = Some(true);
 231        });
 232        let mut cx = EditorLspTestContext::new(
 233            Arc::into_inner(rust_lang()).unwrap(),
 234            lsp::ServerCapabilities::default(),
 235            cx,
 236        )
 237        .await;
 238
 239        cx.set_state(indoc! {r#"ˇuse std::{collections::HashMap, future::Future};
 240
 241fn main() {
 242    let a = one((), { () }, ());
 243    println!("{a}");
 244    println!("{a}");
 245    for i in 0..a {
 246        println!("{i}");
 247    }
 248
 249    let b = {
 250        {
 251            {
 252                [([([([([([([([([([((), ())])])])])])])])])])]
 253            }
 254        }
 255    };
 256}
 257
 258#[rustfmt::skip]
 259fn one(a: (), (): (), c: ()) -> usize { 1 }
 260
 261fn two<T>(a: HashMap<String, Vec<Option<T>>>) -> usize
 262where
 263    T: Future<Output = HashMap<String, Vec<Option<Box<()>>>>>,
 264{
 265    2
 266}
 267"#});
 268        cx.executor().advance_clock(Duration::from_millis(100));
 269        cx.executor().run_until_parked();
 270
 271        assert_eq!(
 272            r#"use std::«1{collections::HashMap, future::Future}1»;
 273
 274fn main«1()1» «1{
 275    let a = one«2(«3()3», «3{ «4()4» }3», «3()3»)2»;
 276    println!«2("{a}")2»;
 277    println!«2("{a}")2»;
 278    for i in 0..a «2{
 279        println!«3("{i}")3»;
 280    }2»
 281
 282    let b = «2{
 283        «3{
 284            «4{
 285                «5[«6(«7[«1(«2[«3(«4[«5(«6[«7(«1[«2(«3[«4(«5[«6(«7[«1(«2[«3(«4()4», «4()4»)3»]2»)1»]7»)6»]5»)4»]3»)2»]1»)7»]6»)5»]4»)3»]2»)1»]7»)6»]5»
 286            }4»
 287        }3»
 288    }2»;
 289}1»
 290
 291#«1[rustfmt::skip]1»
 292fn one«1(a: «2()2», «2()2»: «2()2», c: «2()2»)1» -> usize «1{ 1 }1»
 293
 294fn two«1<T>1»«1(a: HashMap«2<String, Vec«3<Option«4<T>4»>3»>2»)1» -> usize
 295where
 296    T: Future«1<Output = HashMap«2<String, Vec«3<Option«4<Box«5<«6()6»>5»>4»>3»>2»>1»,
 297«1{
 298    2
 299}1»
 300
 3011 hsla(207.80, 16.20%, 69.19%, 1.00)
 3022 hsla(29.00, 54.00%, 65.88%, 1.00)
 3033 hsla(286.00, 51.00%, 75.25%, 1.00)
 3044 hsla(187.00, 47.00%, 59.22%, 1.00)
 3055 hsla(355.00, 65.00%, 75.94%, 1.00)
 3066 hsla(95.00, 38.00%, 62.00%, 1.00)
 3077 hsla(39.00, 67.00%, 69.00%, 1.00)
 308"#,
 309            &bracket_colors_markup(&mut cx),
 310            "All brackets should be colored based on their depth"
 311        );
 312    }
 313
 314    #[gpui::test]
 315    async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
 316        init_test(cx, |language_settings| {
 317            language_settings.defaults.colorize_brackets = Some(true);
 318        });
 319        let editor = cx.add_window(|window, cx| {
 320            let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
 321            multi_buffer.update(cx, |multi_buffer, cx| {
 322                multi_buffer
 323                    .as_singleton()
 324                    .unwrap()
 325                    .update(cx, |buffer, cx| {
 326                        buffer.set_language(Some(rust_lang()), cx);
 327                    });
 328            });
 329            Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 330        });
 331
 332        cx.executor().advance_clock(Duration::from_millis(100));
 333        cx.executor().run_until_parked();
 334
 335        assert_eq!(
 336            "fn main«1()1» «1{}1»
 3371 hsla(207.80, 16.20%, 69.19%, 1.00)
 338",
 339            editor
 340                .update(cx, |editor, window, cx| {
 341                    editor_bracket_colors_markup(&editor.snapshot(window, cx))
 342                })
 343                .unwrap(),
 344            "File-less buffer should still have its brackets colorized"
 345        );
 346    }
 347
 348    #[gpui::test]
 349    async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
 350        init_test(cx, |language_settings| {
 351            language_settings.defaults.colorize_brackets = Some(true);
 352        });
 353        let mut cx = EditorLspTestContext::new(
 354            Arc::into_inner(markdown_lang()).unwrap(),
 355            lsp::ServerCapabilities::default(),
 356            cx,
 357        )
 358        .await;
 359
 360        cx.set_state(indoc! {r#"ˇ[LLM-powered features](./ai/overview.md), [bring and configure your own API keys](./ai/llm-providers.md#use-your-own-keys)"#});
 361        cx.executor().advance_clock(Duration::from_millis(100));
 362        cx.executor().run_until_parked();
 363
 364        assert_eq!(
 365            r#"«1[LLM-powered features]1»«1(./ai/overview.md)1», «1[bring and configure your own API keys]1»«1(./ai/llm-providers.md#use-your-own-keys)1»
 3661 hsla(207.80, 16.20%, 69.19%, 1.00)
 367"#,
 368            &bracket_colors_markup(&mut cx),
 369            "All markdown brackets should be colored based on their depth"
 370        );
 371
 372        cx.set_state(indoc! {r#"ˇ{{}}"#});
 373        cx.executor().advance_clock(Duration::from_millis(100));
 374        cx.executor().run_until_parked();
 375
 376        assert_eq!(
 377            r#"«1{«2{}2»}1»
 3781 hsla(207.80, 16.20%, 69.19%, 1.00)
 3792 hsla(29.00, 54.00%, 65.88%, 1.00)
 380"#,
 381            &bracket_colors_markup(&mut cx),
 382            "All markdown brackets should be colored based on their depth, again"
 383        );
 384
 385        cx.set_state(indoc! {r#"ˇ('')('')
 386
 387((''))('')
 388
 389('')((''))"#});
 390        cx.executor().advance_clock(Duration::from_millis(100));
 391        cx.executor().run_until_parked();
 392
 393        assert_eq!(
 394            "«1('')1»«1('')1»\n\n«1(«2('')2»)1»«1('')1»\n\n«1('')1»«1(«2('')2»)1»\n1 hsla(207.80, 16.20%, 69.19%, 1.00)\n2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
 395            &bracket_colors_markup(&mut cx),
 396            "Markdown quote pairs should not interfere with parenthesis pairing"
 397        );
 398    }
 399
 400    #[gpui::test]
 401    async fn test_markdown_brackets_in_multiple_hunks(cx: &mut gpui::TestAppContext) {
 402        init_test(cx, |language_settings| {
 403            language_settings.defaults.colorize_brackets = Some(true);
 404        });
 405        let mut cx = EditorLspTestContext::new(
 406            Arc::into_inner(markdown_lang()).unwrap(),
 407            lsp::ServerCapabilities::default(),
 408            cx,
 409        )
 410        .await;
 411
 412        let rows = 100;
 413        let footer = "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n";
 414
 415        let simple_brackets = (0..rows).map(|_| "ˇ[]\n").collect::<String>();
 416        let simple_brackets_highlights = (0..rows).map(|_| "«1[]1»\n").collect::<String>();
 417        cx.set_state(&simple_brackets);
 418        cx.update_editor(|editor, window, cx| {
 419            editor.move_to_end(&MoveToEnd, window, cx);
 420        });
 421        cx.executor().advance_clock(Duration::from_millis(100));
 422        cx.executor().run_until_parked();
 423        assert_eq!(
 424            format!("{simple_brackets_highlights}\n{footer}"),
 425            bracket_colors_markup(&mut cx),
 426            "Simple bracket pairs should be colored"
 427        );
 428
 429        let paired_brackets = (0..rows).map(|_| "ˇ[]()\n").collect::<String>();
 430        let paired_brackets_highlights = (0..rows).map(|_| "«1[]1»«1()1»\n").collect::<String>();
 431        cx.set_state(&paired_brackets);
 432        // Wait for reparse to complete after content change
 433        cx.executor().advance_clock(Duration::from_millis(100));
 434        cx.executor().run_until_parked();
 435        cx.update_editor(|editor, _, cx| {
 436            // Force invalidation of bracket cache after reparse
 437            editor.colorize_brackets(true, cx);
 438        });
 439        // Scroll to beginning to fetch first chunks
 440        cx.update_editor(|editor, window, cx| {
 441            editor.move_to_beginning(&MoveToBeginning, window, cx);
 442        });
 443        cx.executor().advance_clock(Duration::from_millis(100));
 444        cx.executor().run_until_parked();
 445        // Scroll to end to fetch remaining chunks
 446        cx.update_editor(|editor, window, cx| {
 447            editor.move_to_end(&MoveToEnd, window, cx);
 448        });
 449        cx.executor().advance_clock(Duration::from_millis(100));
 450        cx.executor().run_until_parked();
 451        assert_eq!(
 452            format!("{paired_brackets_highlights}\n{footer}"),
 453            bracket_colors_markup(&mut cx),
 454            "Paired bracket pairs should be colored"
 455        );
 456    }
 457
 458    #[gpui::test]
 459    async fn test_bracket_colorization_after_language_swap(cx: &mut gpui::TestAppContext) {
 460        init_test(cx, |language_settings| {
 461            language_settings.defaults.colorize_brackets = Some(true);
 462        });
 463
 464        let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
 465        language_registry.add(markdown_lang());
 466        language_registry.add(rust_lang());
 467
 468        let mut cx = EditorTestContext::new(cx).await;
 469        cx.update_buffer(|buffer, cx| {
 470            buffer.set_language_registry(language_registry.clone());
 471            buffer.set_language(Some(markdown_lang()), cx);
 472        });
 473
 474        cx.set_state(indoc! {r#"
 475            fn main() {
 476                let v: Vec<Stringˇ> = vec![];
 477            }
 478        "#});
 479        cx.executor().advance_clock(Duration::from_millis(100));
 480        cx.executor().run_until_parked();
 481
 482        assert_eq!(
 483            r#"fn main«1()1» «1{
 484    let v: Vec<String> = vec!«2[]2»;
 485}1»
 486
 4871 hsla(207.80, 16.20%, 69.19%, 1.00)
 4882 hsla(29.00, 54.00%, 65.88%, 1.00)
 489"#,
 490            &bracket_colors_markup(&mut cx),
 491            "Markdown does not colorize <> brackets"
 492        );
 493
 494        cx.update_buffer(|buffer, cx| {
 495            buffer.set_language(Some(rust_lang()), cx);
 496        });
 497        cx.executor().advance_clock(Duration::from_millis(100));
 498        cx.executor().run_until_parked();
 499
 500        assert_eq!(
 501            r#"fn main«1()1» «1{
 502    let v: Vec«2<String>2» = vec!«2[]2»;
 503}1»
 504
 5051 hsla(207.80, 16.20%, 69.19%, 1.00)
 5062 hsla(29.00, 54.00%, 65.88%, 1.00)
 507"#,
 508            &bracket_colors_markup(&mut cx),
 509            "After switching to Rust, <> brackets are now colorized"
 510        );
 511    }
 512
 513    #[gpui::test]
 514    async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
 515        init_test(cx, |language_settings| {
 516            language_settings.defaults.colorize_brackets = Some(true);
 517        });
 518        let mut cx = EditorLspTestContext::new(
 519            Arc::into_inner(rust_lang()).unwrap(),
 520            lsp::ServerCapabilities::default(),
 521            cx,
 522        )
 523        .await;
 524
 525        cx.set_state(indoc! {r#"
 526struct Foo<'a, T> {
 527    data: Vec<Option<&'a T>>,
 528}
 529
 530fn process_data() {
 531    let map:ˇ
 532}
 533"#});
 534
 535        cx.update_editor(|editor, window, cx| {
 536            editor.handle_input(" Result<", window, cx);
 537        });
 538        cx.executor().advance_clock(Duration::from_millis(100));
 539        cx.executor().run_until_parked();
 540        assert_eq!(
 541            indoc! {r#"
 542struct Foo«1<'a, T>1» «1{
 543    data: Vec«2<Option«3<&'a T>3»>2»,
 544}1»
 545
 546fn process_data«1()1» «1{
 547    let map: Result<
 548}1»
 549
 5501 hsla(207.80, 16.20%, 69.19%, 1.00)
 5512 hsla(29.00, 54.00%, 65.88%, 1.00)
 5523 hsla(286.00, 51.00%, 75.25%, 1.00)
 553"#},
 554            &bracket_colors_markup(&mut cx),
 555            "Brackets without pairs should be ignored and not colored"
 556        );
 557
 558        cx.update_editor(|editor, window, cx| {
 559            editor.handle_input("Option<Foo<'_, ()", window, cx);
 560        });
 561        cx.executor().advance_clock(Duration::from_millis(100));
 562        cx.executor().run_until_parked();
 563        assert_eq!(
 564            indoc! {r#"
 565struct Foo«1<'a, T>1» «1{
 566    data: Vec«2<Option«3<&'a T>3»>2»,
 567}1»
 568
 569fn process_data«1()1» «1{
 570    let map: Result<Option<Foo<'_, «2()2»
 571}1»
 572
 5731 hsla(207.80, 16.20%, 69.19%, 1.00)
 5742 hsla(29.00, 54.00%, 65.88%, 1.00)
 5753 hsla(286.00, 51.00%, 75.25%, 1.00)
 576"#},
 577            &bracket_colors_markup(&mut cx),
 578        );
 579
 580        cx.update_editor(|editor, window, cx| {
 581            editor.handle_input(">", window, cx);
 582        });
 583        cx.executor().advance_clock(Duration::from_millis(100));
 584        cx.executor().run_until_parked();
 585        assert_eq!(
 586            indoc! {r#"
 587struct Foo«1<'a, T>1» «1{
 588    data: Vec«2<Option«3<&'a T>3»>2»,
 589}1»
 590
 591fn process_data«1()1» «1{
 592    let map: Result<Option<Foo«2<'_, «3()3»>2»
 593}1»
 594
 5951 hsla(207.80, 16.20%, 69.19%, 1.00)
 5962 hsla(29.00, 54.00%, 65.88%, 1.00)
 5973 hsla(286.00, 51.00%, 75.25%, 1.00)
 598"#},
 599            &bracket_colors_markup(&mut cx),
 600            "When brackets start to get closed, inner brackets are re-colored based on their depth"
 601        );
 602
 603        cx.update_editor(|editor, window, cx| {
 604            editor.handle_input(">", window, cx);
 605        });
 606        cx.executor().advance_clock(Duration::from_millis(100));
 607        cx.executor().run_until_parked();
 608        assert_eq!(
 609            indoc! {r#"
 610struct Foo«1<'a, T>1» «1{
 611    data: Vec«2<Option«3<&'a T>3»>2»,
 612}1»
 613
 614fn process_data«1()1» «1{
 615    let map: Result<Option«2<Foo«3<'_, «4()4»>3»>2»
 616}1»
 617
 6181 hsla(207.80, 16.20%, 69.19%, 1.00)
 6192 hsla(29.00, 54.00%, 65.88%, 1.00)
 6203 hsla(286.00, 51.00%, 75.25%, 1.00)
 6214 hsla(187.00, 47.00%, 59.22%, 1.00)
 622"#},
 623            &bracket_colors_markup(&mut cx),
 624        );
 625
 626        cx.update_editor(|editor, window, cx| {
 627            editor.handle_input(", ()> = unimplemented!();", window, cx);
 628        });
 629        cx.executor().advance_clock(Duration::from_millis(100));
 630        cx.executor().run_until_parked();
 631        assert_eq!(
 632            indoc! {r#"
 633struct Foo«1<'a, T>1» «1{
 634    data: Vec«2<Option«3<&'a T>3»>2»,
 635}1»
 636
 637fn process_data«1()1» «1{
 638    let map: Result«2<Option«3<Foo«4<'_, «5()5»>4»>3», «3()3»>2» = unimplemented!«2()2»;
 639}1»
 640
 6411 hsla(207.80, 16.20%, 69.19%, 1.00)
 6422 hsla(29.00, 54.00%, 65.88%, 1.00)
 6433 hsla(286.00, 51.00%, 75.25%, 1.00)
 6444 hsla(187.00, 47.00%, 59.22%, 1.00)
 6455 hsla(355.00, 65.00%, 75.94%, 1.00)
 646"#},
 647            &bracket_colors_markup(&mut cx),
 648        );
 649    }
 650
 651    #[gpui::test]
 652    async fn test_bracket_colorization_large_block(cx: &mut gpui::TestAppContext) {
 653        // Each padded comment line is 27 bytes; 620 lines = 16740 bytes,
 654        // just over MAX_BYTES_TO_QUERY (16 KB) with head/tail overhead.
 655        let comment_lines = 620;
 656
 657        init_test(cx, |language_settings| {
 658            language_settings.defaults.colorize_brackets = Some(true);
 659        });
 660        let mut cx = EditorLspTestContext::new(
 661            Arc::into_inner(rust_lang()).unwrap(),
 662            lsp::ServerCapabilities::default(),
 663            cx,
 664        )
 665        .await;
 666
 667        cx.set_state(&separate_with_comment_lines(
 668            indoc! {r#"
 669mod foo {
 670    ˇfn process_data_1() {
 671        let map: Option<Vec<()>> = None;
 672    }
 673"#},
 674            indoc! {r#"
 675    fn process_data_2() {
 676        let map: Option<Vec<()>> = None;
 677    }
 678}
 679"#},
 680            comment_lines,
 681        ));
 682
 683        let colored_head = "mod foo «1{\n\
 684                            \x20   fn process_data_1«2()2» «2{\n\
 685                            \x20       let map: Option«3<Vec«4<«5()5»>4»>3» = None;\n\
 686                            \x20   }2»";
 687        let uncolored_tail = "    fn process_data_2() {\n\
 688                              \x20       let map: Option<Vec<()>> = None;\n\
 689                              \x20   }\n\
 690                              }1»";
 691        let colored_tail = "    fn process_data_2«2()2» «2{\n\
 692                            \x20       let map: Option«3<Vec«4<«5()5»>4»>3» = None;\n\
 693                            \x20   }2»\n\
 694                            }1»";
 695
 696        cx.executor().advance_clock(Duration::from_millis(100));
 697        cx.executor().run_until_parked();
 698        let markup = bracket_colors_markup(&mut cx);
 699        let relevant = filter_bracket_relevant_lines(&markup);
 700        assert_eq!(
 701            relevant,
 702            format!("{colored_head}\n{uncolored_tail}"),
 703            "Top chunk: visible brackets should be colorized even when the \
 704             enclosing block exceeds MAX_BYTES_TO_QUERY"
 705        );
 706
 707        cx.update_editor(|editor, window, cx| {
 708            editor.move_to_end(&MoveToEnd, window, cx);
 709            editor.move_up(&MoveUp, window, cx);
 710        });
 711        cx.executor().advance_clock(Duration::from_millis(100));
 712        cx.executor().run_until_parked();
 713        let markup = bracket_colors_markup(&mut cx);
 714        let relevant = filter_bracket_relevant_lines(&markup);
 715        assert_eq!(
 716            relevant,
 717            format!("{colored_head}\n{colored_tail}"),
 718            "After scrolling to bottom, both chunks should have bracket \
 719             highlights across a large block"
 720        );
 721    }
 722
 723    #[gpui::test]
 724    async fn test_bracket_colorization_chunks(cx: &mut gpui::TestAppContext) {
 725        let comment_lines = 100;
 726
 727        init_test(cx, |language_settings| {
 728            language_settings.defaults.colorize_brackets = Some(true);
 729        });
 730        let mut cx = EditorLspTestContext::new(
 731            Arc::into_inner(rust_lang()).unwrap(),
 732            lsp::ServerCapabilities::default(),
 733            cx,
 734        )
 735        .await;
 736
 737        cx.set_state(&separate_with_comment_lines(
 738            indoc! {r#"
 739mod foo {
 740    ˇfn process_data_1() {
 741        let map: Option<Vec<()>> = None;
 742    }
 743"#},
 744            indoc! {r#"
 745    fn process_data_2() {
 746        let map: Option<Vec<()>> = None;
 747    }
 748}
 749"#},
 750            comment_lines,
 751        ));
 752
 753        cx.executor().advance_clock(Duration::from_millis(100));
 754        cx.executor().run_until_parked();
 755        assert_eq!(
 756            &separate_with_comment_lines(
 757                indoc! {r#"
 758mod foo «1{
 759    fn process_data_1«2()2» «2{
 760        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 761    }2»
 762"#},
 763                indoc! {r#"
 764    fn process_data_2() {
 765        let map: Option<Vec<()>> = None;
 766    }
 767}1»
 768
 7691 hsla(207.80, 16.20%, 69.19%, 1.00)
 7702 hsla(29.00, 54.00%, 65.88%, 1.00)
 7713 hsla(286.00, 51.00%, 75.25%, 1.00)
 7724 hsla(187.00, 47.00%, 59.22%, 1.00)
 7735 hsla(355.00, 65.00%, 75.94%, 1.00)
 774"#},
 775                comment_lines,
 776            ),
 777            &bracket_colors_markup(&mut cx),
 778            "First, the only visible chunk is getting the bracket highlights"
 779        );
 780
 781        cx.update_editor(|editor, window, cx| {
 782            editor.move_to_end(&MoveToEnd, window, cx);
 783            editor.move_up(&MoveUp, window, cx);
 784        });
 785        cx.executor().advance_clock(Duration::from_millis(100));
 786        cx.executor().run_until_parked();
 787        assert_eq!(
 788            &separate_with_comment_lines(
 789                indoc! {r#"
 790mod foo «1{
 791    fn process_data_1«2()2» «2{
 792        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 793    }2»
 794"#},
 795                indoc! {r#"
 796    fn process_data_2«2()2» «2{
 797        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 798    }2»
 799}1»
 800
 8011 hsla(207.80, 16.20%, 69.19%, 1.00)
 8022 hsla(29.00, 54.00%, 65.88%, 1.00)
 8033 hsla(286.00, 51.00%, 75.25%, 1.00)
 8044 hsla(187.00, 47.00%, 59.22%, 1.00)
 8055 hsla(355.00, 65.00%, 75.94%, 1.00)
 806"#},
 807                comment_lines,
 808            ),
 809            &bracket_colors_markup(&mut cx),
 810            "After scrolling to the bottom, both chunks should have the highlights"
 811        );
 812
 813        cx.update_editor(|editor, window, cx| {
 814            editor.handle_input("{{}}}", window, cx);
 815        });
 816        cx.executor().advance_clock(Duration::from_millis(100));
 817        cx.executor().run_until_parked();
 818        assert_eq!(
 819            &separate_with_comment_lines(
 820                indoc! {r#"
 821mod foo «1{
 822    fn process_data_1() {
 823        let map: Option<Vec<()>> = None;
 824    }
 825"#},
 826                indoc! {r#"
 827    fn process_data_2«2()2» «2{
 828        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 829    }
 830    «3{«4{}4»}3»}2»}1»
 831
 8321 hsla(207.80, 16.20%, 69.19%, 1.00)
 8332 hsla(29.00, 54.00%, 65.88%, 1.00)
 8343 hsla(286.00, 51.00%, 75.25%, 1.00)
 8354 hsla(187.00, 47.00%, 59.22%, 1.00)
 8365 hsla(355.00, 65.00%, 75.94%, 1.00)
 837"#},
 838                comment_lines,
 839            ),
 840            &bracket_colors_markup(&mut cx),
 841            "First chunk's brackets are invalidated after an edit, and only 2nd (visible) chunk is re-colorized"
 842        );
 843
 844        cx.update_editor(|editor, window, cx| {
 845            editor.move_to_beginning(&MoveToBeginning, window, cx);
 846        });
 847        cx.executor().advance_clock(Duration::from_millis(100));
 848        cx.executor().run_until_parked();
 849        assert_eq!(
 850            &separate_with_comment_lines(
 851                indoc! {r#"
 852mod foo «1{
 853    fn process_data_1«2()2» «2{
 854        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 855    }2»
 856"#},
 857                indoc! {r#"
 858    fn process_data_2«2()2» «2{
 859        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 860    }
 861    «3{«4{}4»}3»}2»}1»
 862
 8631 hsla(207.80, 16.20%, 69.19%, 1.00)
 8642 hsla(29.00, 54.00%, 65.88%, 1.00)
 8653 hsla(286.00, 51.00%, 75.25%, 1.00)
 8664 hsla(187.00, 47.00%, 59.22%, 1.00)
 8675 hsla(355.00, 65.00%, 75.94%, 1.00)
 868"#},
 869                comment_lines,
 870            ),
 871            &bracket_colors_markup(&mut cx),
 872            "Scrolling back to top should re-colorize all chunks' brackets"
 873        );
 874
 875        cx.update(|_, cx| {
 876            SettingsStore::update_global(cx, |store, cx| {
 877                store.update_user_settings(cx, |settings| {
 878                    settings.project.all_languages.defaults.colorize_brackets = Some(false);
 879                });
 880            });
 881        });
 882        cx.executor().run_until_parked();
 883        assert_eq!(
 884            &separate_with_comment_lines(
 885                indoc! {r#"
 886mod foo {
 887    fn process_data_1() {
 888        let map: Option<Vec<()>> = None;
 889    }
 890"#},
 891                r#"    fn process_data_2() {
 892        let map: Option<Vec<()>> = None;
 893    }
 894    {{}}}}
 895
 896"#,
 897                comment_lines,
 898            ),
 899            &bracket_colors_markup(&mut cx),
 900            "Turning bracket colorization off should remove all bracket colors"
 901        );
 902
 903        cx.update(|_, cx| {
 904            SettingsStore::update_global(cx, |store, cx| {
 905                store.update_user_settings(cx, |settings| {
 906                    settings.project.all_languages.defaults.colorize_brackets = Some(true);
 907                });
 908            });
 909        });
 910        cx.executor().run_until_parked();
 911        assert_eq!(
 912            &separate_with_comment_lines(
 913                indoc! {r#"
 914mod foo «1{
 915    fn process_data_1«2()2» «2{
 916        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 917    }2»
 918"#},
 919                r#"    fn process_data_2() {
 920        let map: Option<Vec<()>> = None;
 921    }
 922    {{}}}}1»
 923
 9241 hsla(207.80, 16.20%, 69.19%, 1.00)
 9252 hsla(29.00, 54.00%, 65.88%, 1.00)
 9263 hsla(286.00, 51.00%, 75.25%, 1.00)
 9274 hsla(187.00, 47.00%, 59.22%, 1.00)
 9285 hsla(355.00, 65.00%, 75.94%, 1.00)
 929"#,
 930                comment_lines,
 931            ),
 932            &bracket_colors_markup(&mut cx),
 933            "Turning bracket colorization back on refreshes the visible excerpts' bracket colors"
 934        );
 935    }
 936
 937    #[gpui::test]
 938    async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
 939        init_test(cx, |language_settings| {
 940            language_settings.defaults.colorize_brackets = Some(true);
 941        });
 942        let mut cx = EditorLspTestContext::new(
 943            Arc::into_inner(rust_lang()).unwrap(),
 944            lsp::ServerCapabilities::default(),
 945            cx,
 946        )
 947        .await;
 948
 949        // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
 950        cx.set_state(indoc! {r# 951            pub(crate) fn inlay_hints(
 952                db: &RootDatabase,
 953                file_id: FileId,
 954                range_limit: Option<TextRange>,
 955                config: &InlayHintsConfig,
 956            ) -> Vec<InlayHint> {
 957                let _p = tracing::info_span!("inlay_hints").entered();
 958                let sema = Semantics::new(db);
 959                let file_id = sema
 960                    .attach_first_edition(file_id)
 961                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 962                let file = sema.parse(file_id);
 963                let file = file.syntax();
 964
 965                let mut acc = Vec::new();
 966
 967                let Some(scope) = sema.scope(file) else {
 968                    return acc;
 969                };
 970                let famous_defs = FamousDefs(&sema, scope.krate());
 971                let display_target = famous_defs.1.to_display_target(sema.db);
 972
 973                let ctx = &mut InlayHintCtx::default();
 974                let mut hints = |event| {
 975                    if let Some(node) = handle_event(ctx, event) {
 976                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 977                    }
 978                };
 979                let mut preorder = file.preorder();
 980                salsa::attach(sema.db, || {
 981                    while let Some(event) = preorder.next() {
 982                        if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
 983                        {
 984                            preorder.skip_subtree();
 985                            continue;
 986                        }
 987                        hints(event);
 988                    }
 989                });
 990                if let Some(range_limit) = range_limit {
 991                    acc.retain(|hint| range_limit.contains_range(hint.range));
 992                }
 993                acc
 994            }
 995
 996            #[derive(Default)]
 997            struct InlayHintCtx {
 998                lifetime_stacks: Vec<Vec<SmolStr>>,
 999                extern_block_parent: Option<ast::ExternBlock>,
1000            }
1001
1002            pub(crate) fn inlay_hints_resolve(
1003                db: &RootDatabase,
1004                file_id: FileId,
1005                resolve_range: TextRange,
1006                hash: u64,
1007                config: &InlayHintsConfig,
1008                hasher: impl Fn(&InlayHint) -> u64,
1009            ) -> Option<InlayHint> {
1010                let _p = tracing::info_span!("inlay_hints_resolve").entered();
1011                let sema = Semantics::new(db);
1012                let file_id = sema
1013                    .attach_first_edition(file_id)
1014                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
1015                let file = sema.parse(file_id);
1016                let file = file.syntax();
1017
1018                let scope = sema.scope(file)?;
1019                let famous_defs = FamousDefs(&sema, scope.krate());
1020                let mut acc = Vec::new();
1021
1022                let display_target = famous_defs.1.to_display_target(sema.db);
1023
1024                let ctx = &mut InlayHintCtx::default();
1025                let mut hints = |event| {
1026                    if let Some(node) = handle_event(ctx, event) {
1027                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
1028                    }
1029                };
1030
1031                let mut preorder = file.preorder();
1032                while let Some(event) = preorder.next() {
1033                    // This can miss some hints that require the parent of the range to calculate
1034                    if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
1035                    {
1036                        preorder.skip_subtree();
1037                        continue;
1038                    }
1039                    hints(event);
1040                }
1041                acc.into_iter().find(|hint| hasher(hint) == hash)
1042            }
1043
1044            fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
1045                match node {
1046                    WalkEvent::Enter(node) => {
1047                        if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
1048                            let params = node
1049                                .generic_param_list()
1050                                .map(|it| {
1051                                    it.lifetime_params()
1052                                        .filter_map(|it| {
1053                                            it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
1054                                        })
1055                                        .collect()
1056                                })
1057                                .unwrap_or_default();
1058                            ctx.lifetime_stacks.push(params);
1059                        }
1060                        if let Some(node) = ast::ExternBlock::cast(node.clone()) {
1061                            ctx.extern_block_parent = Some(node);
1062                        }
1063                        Some(node)
1064                    }
1065                    WalkEvent::Leave(n) => {
1066                        if ast::AnyHasGenericParams::can_cast(n.kind()) {
1067                            ctx.lifetime_stacks.pop();
1068                        }
1069                        if ast::ExternBlock::can_cast(n.kind()) {
1070                            ctx.extern_block_parent = None;
1071                        }
1072                        None
1073                    }
1074                }
1075            }
1076
1077            // At some point when our hir infra is fleshed out enough we should flip this and traverse the
1078            // HIR instead of the syntax tree.
1079            fn hints(
1080                hints: &mut Vec<InlayHint>,
1081                ctx: &mut InlayHintCtx,
1082                famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
1083                config: &InlayHintsConfig,
1084                file_id: EditionedFileId,
1085                display_target: DisplayTarget,
1086                node: SyntaxNode,
1087            ) {
1088                closing_brace::hints(
1089                    hints,
1090                    sema,
1091                    config,
1092                    display_target,
1093                    InRealFile { file_id, value: node.clone() },
1094                );
1095                if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
1096                    generic_param::hints(hints, famous_defs, config, any_has_generic_args);
1097                }
1098
1099                match_ast! {
1100                    match node {
1101                        ast::Expr(expr) => {
1102                            chaining::hints(hints, famous_defs, config, display_target, &expr);
1103                            adjustment::hints(hints, famous_defs, config, display_target, &expr);
1104                            match expr {
1105                                ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
1106                                ast::Expr::MethodCallExpr(it) => {
1107                                    param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
1108                                }
1109                                ast::Expr::ClosureExpr(it) => {
1110                                    closure_captures::hints(hints, famous_defs, config, it.clone());
1111                                    closure_ret::hints(hints, famous_defs, config, display_target, it)
1112                                },
1113                                ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
1114                                _ => Some(()),
1115                            }
1116                        },
1117                        ast::Pat(it) => {
1118                            binding_mode::hints(hints, famous_defs, config, &it);
1119                            match it {
1120                                ast::Pat::IdentPat(it) => {
1121                                    bind_pat::hints(hints, famous_defs, config, display_target, &it);
1122                                }
1123                                ast::Pat::RangePat(it) => {
1124                                    range_exclusive::hints(hints, famous_defs, config, it);
1125                                }
1126                                _ => {}
1127                            }
1128                            Some(())
1129                        },
1130                        ast::Item(it) => match it {
1131                            ast::Item::Fn(it) => {
1132                                implicit_drop::hints(hints, famous_defs, config, display_target, &it);
1133                                if let Some(extern_block) = &ctx.extern_block_parent {
1134                                    extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
1135                                }
1136                                lifetime::fn_hints(hints, ctx, famous_defs, config,  it)
1137                            },
1138                            ast::Item::Static(it) => {
1139                                if let Some(extern_block) = &ctx.extern_block_parent {
1140                                    extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
1141                                }
1142                                implicit_static::hints(hints, famous_defs, config,  Either::Left(it))
1143                            },
1144                            ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
1145                            ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
1146                            ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
1147                            _ => None,
1148                        },
1149                        // trait object type elisions
1150                        ast::Type(ty) => match ty {
1151                            ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config,  ptr),
1152                            ast::Type::PathType(path) => {
1153                                lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
1154                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
1155                                Some(())
1156                            },
1157                            ast::Type::DynTraitType(dyn_) => {
1158                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
1159                                Some(())
1160                            },
1161                            _ => Some(()),
1162                        },
1163                        ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config,  it),
1164                        _ => Some(()),
1165                    }
1166                };
1167            }
1168        "#});
1169        cx.executor().advance_clock(Duration::from_millis(100));
1170        cx.executor().run_until_parked();
1171
1172        let actual_ranges = cx.update_editor(|editor, window, cx| {
1173            editor
1174                .snapshot(window, cx)
1175                .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1176        });
1177
1178        let mut highlighted_brackets = HashMap::default();
1179        for (color, range) in actual_ranges.iter().cloned() {
1180            highlighted_brackets.insert(range, color);
1181        }
1182
1183        let last_bracket = actual_ranges
1184            .iter()
1185            .max_by_key(|(_, p)| p.end.row)
1186            .unwrap()
1187            .clone();
1188
1189        cx.update_editor(|editor, window, cx| {
1190            let was_scrolled = editor.set_scroll_position(
1191                gpui::Point::new(0.0, last_bracket.1.end.row as f64 * 2.0),
1192                window,
1193                cx,
1194            );
1195            assert!(was_scrolled.0);
1196        });
1197        cx.executor().advance_clock(Duration::from_millis(100));
1198        cx.executor().run_until_parked();
1199
1200        let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
1201            editor
1202                .snapshot(window, cx)
1203                .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1204        });
1205        let new_last_bracket = ranges_after_scrolling
1206            .iter()
1207            .max_by_key(|(_, p)| p.end.row)
1208            .unwrap()
1209            .clone();
1210
1211        assert_ne!(
1212            last_bracket, new_last_bracket,
1213            "After scrolling down, we should have highlighted more brackets"
1214        );
1215
1216        cx.update_editor(|editor, window, cx| {
1217            let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
1218            assert!(was_scrolled.0);
1219        });
1220
1221        for _ in 0..200 {
1222            cx.update_editor(|editor, window, cx| {
1223                editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
1224            });
1225            cx.executor().advance_clock(Duration::from_millis(100));
1226            cx.executor().run_until_parked();
1227
1228            let colored_brackets = cx.update_editor(|editor, window, cx| {
1229                editor
1230                    .snapshot(window, cx)
1231                    .all_text_highlight_ranges(&|key| {
1232                        matches!(key, HighlightKey::ColorizeBracket(_))
1233                    })
1234            });
1235            for (color, range) in colored_brackets.clone() {
1236                assert!(
1237                    highlighted_brackets.entry(range).or_insert(color) == &color,
1238                    "Colors should stay consistent while scrolling!"
1239                );
1240            }
1241
1242            let snapshot = cx.update_editor(|editor, window, cx| editor.snapshot(window, cx));
1243            let scroll_position = snapshot.scroll_position();
1244            let visible_lines =
1245                cx.update_editor(|editor, _, _| editor.visible_line_count().unwrap());
1246            let visible_range = DisplayRow(scroll_position.y as u32)
1247                ..DisplayRow((scroll_position.y + visible_lines) as u32);
1248
1249            let current_highlighted_bracket_set: HashSet<Point> = HashSet::from_iter(
1250                colored_brackets
1251                    .iter()
1252                    .flat_map(|(_, range)| [range.start, range.end]),
1253            );
1254
1255            for highlight_range in highlighted_brackets.keys().filter(|bracket_range| {
1256                visible_range.contains(&bracket_range.start.to_display_point(&snapshot).row())
1257                    || visible_range.contains(&bracket_range.end.to_display_point(&snapshot).row())
1258            }) {
1259                assert!(
1260                    current_highlighted_bracket_set.contains(&highlight_range.start)
1261                        || current_highlighted_bracket_set.contains(&highlight_range.end),
1262                    "Should not lose highlights while scrolling in the visible range!"
1263                );
1264            }
1265
1266            let buffer_snapshot = snapshot.buffer().as_singleton().unwrap();
1267            for bracket_match in buffer_snapshot
1268                .fetch_bracket_ranges(
1269                    snapshot
1270                        .display_point_to_point(
1271                            DisplayPoint::new(visible_range.start, 0),
1272                            Bias::Left,
1273                        )
1274                        .to_offset(&buffer_snapshot)
1275                        ..snapshot
1276                            .display_point_to_point(
1277                                DisplayPoint::new(
1278                                    visible_range.end,
1279                                    snapshot.line_len(visible_range.end),
1280                                ),
1281                                Bias::Right,
1282                            )
1283                            .to_offset(&buffer_snapshot),
1284                    None,
1285                )
1286                .iter()
1287                .flat_map(|entry| entry.1)
1288                .filter(|bracket_match| bracket_match.color_index.is_some())
1289            {
1290                let start = bracket_match.open_range.to_point(buffer_snapshot);
1291                let end = bracket_match.close_range.to_point(buffer_snapshot);
1292                let start_bracket = colored_brackets.iter().find(|(_, range)| *range == start);
1293                assert!(
1294                    start_bracket.is_some(),
1295                    "Existing bracket start in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1296                    buffer_snapshot
1297                        .text_for_range(start.start..end.end)
1298                        .collect::<String>(),
1299                    start
1300                );
1301
1302                let end_bracket = colored_brackets.iter().find(|(_, range)| *range == end);
1303                assert!(
1304                    end_bracket.is_some(),
1305                    "Existing bracket end in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1306                    buffer_snapshot
1307                        .text_for_range(start.start..end.end)
1308                        .collect::<String>(),
1309                    start
1310                );
1311
1312                assert_eq!(
1313                    start_bracket.unwrap().0,
1314                    end_bracket.unwrap().0,
1315                    "Bracket pair should be highlighted the same color!"
1316                )
1317            }
1318        }
1319    }
1320
1321    #[gpui::test]
1322    async fn test_multi_buffer(cx: &mut gpui::TestAppContext) {
1323        let comment_lines = 100;
1324
1325        init_test(cx, |language_settings| {
1326            language_settings.defaults.colorize_brackets = Some(true);
1327        });
1328        let fs = FakeFs::new(cx.background_executor.clone());
1329        fs.insert_tree(
1330            path!("/a"),
1331            json!({
1332                "main.rs": "fn main() {{()}}",
1333                "lib.rs": separate_with_comment_lines(
1334                    indoc! {r#"
1335    mod foo {
1336        fn process_data_1() {
1337            let map: Option<Vec<()>> = None;
1338            // a
1339            // b
1340            // c
1341        }
1342    "#},
1343                    indoc! {r#"
1344        fn process_data_2() {
1345            let other_map: Option<Vec<()>> = None;
1346        }
1347    }
1348    "#},
1349                    comment_lines,
1350                )
1351            }),
1352        )
1353        .await;
1354
1355        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1356        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1357        language_registry.add(rust_lang());
1358
1359        let buffer_1 = project
1360            .update(cx, |project, cx| {
1361                project.open_local_buffer(path!("/a/lib.rs"), cx)
1362            })
1363            .await
1364            .unwrap();
1365        let buffer_2 = project
1366            .update(cx, |project, cx| {
1367                project.open_local_buffer(path!("/a/main.rs"), cx)
1368            })
1369            .await
1370            .unwrap();
1371
1372        let multi_buffer = cx.new(|cx| {
1373            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1374            multi_buffer.set_excerpts_for_path(
1375                PathKey::sorted(0),
1376                buffer_2.clone(),
1377                [Point::new(0, 0)..Point::new(1, 0)],
1378                0,
1379                cx,
1380            );
1381
1382            let excerpt_rows = 5;
1383            let rest_of_first_except_rows = 3;
1384            multi_buffer.set_excerpts_for_path(
1385                PathKey::sorted(1),
1386                buffer_1.clone(),
1387                [
1388                    Point::new(0, 0)..Point::new(excerpt_rows, 0),
1389                    Point::new(
1390                        comment_lines as u32 + excerpt_rows + rest_of_first_except_rows,
1391                        0,
1392                    )
1393                        ..Point::new(
1394                            comment_lines as u32
1395                                + excerpt_rows
1396                                + rest_of_first_except_rows
1397                                + excerpt_rows,
1398                            0,
1399                        ),
1400                ],
1401                0,
1402                cx,
1403            );
1404            multi_buffer
1405        });
1406
1407        let editor = cx.add_window(|window, cx| {
1408            Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1409        });
1410        cx.executor().advance_clock(Duration::from_millis(100));
1411        cx.executor().run_until_parked();
1412
1413        let editor_snapshot = editor
1414            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1415            .unwrap();
1416        assert_eq!(
1417            indoc! {r#"
1418
1419
1420fn main«1()1» «1{«2{«3()3»}2»}1»
1421
1422
1423mod foo «1{
1424    fn process_data_1«2()2» «2{
1425        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1426        // a
1427        // b
1428        // c
1429
1430    fn process_data_2«2()2» «2{
1431        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1432    }2»
1433}1»
1434
14351 hsla(207.80, 16.20%, 69.19%, 1.00)
14362 hsla(29.00, 54.00%, 65.88%, 1.00)
14373 hsla(286.00, 51.00%, 75.25%, 1.00)
14384 hsla(187.00, 47.00%, 59.22%, 1.00)
14395 hsla(355.00, 65.00%, 75.94%, 1.00)
1440"#,},
1441            &editor_bracket_colors_markup(&editor_snapshot),
1442            "Multi buffers should have their brackets colored even if no excerpts contain the bracket counterpart (after fn `process_data_2()`) \
1443or if the buffer pair spans across multiple excerpts (the one after `mod foo`)"
1444        );
1445
1446        editor
1447            .update(cx, |editor, window, cx| {
1448                editor.handle_input("{[]", window, cx);
1449            })
1450            .unwrap();
1451        cx.executor().advance_clock(Duration::from_millis(100));
1452        cx.executor().run_until_parked();
1453        let editor_snapshot = editor
1454            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1455            .unwrap();
1456        assert_eq!(
1457            indoc! {r#"
1458
1459
1460{«1[]1»fn main«1()1» «1{«2{«3()3»}2»}1»
1461
1462
1463mod foo «1{
1464    fn process_data_1«2()2» «2{
1465        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1466        // a
1467        // b
1468        // c
1469
1470    fn process_data_2«2()2» «2{
1471        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1472    }2»
1473}1»
1474
14751 hsla(207.80, 16.20%, 69.19%, 1.00)
14762 hsla(29.00, 54.00%, 65.88%, 1.00)
14773 hsla(286.00, 51.00%, 75.25%, 1.00)
14784 hsla(187.00, 47.00%, 59.22%, 1.00)
14795 hsla(355.00, 65.00%, 75.94%, 1.00)
1480"#,},
1481            &editor_bracket_colors_markup(&editor_snapshot),
1482        );
1483
1484        cx.update(|cx| {
1485            let theme = cx.theme().name.clone();
1486            SettingsStore::update_global(cx, |store, cx| {
1487                store.update_user_settings(cx, |settings| {
1488                    settings.theme.theme_overrides = HashMap::from_iter([(
1489                        theme.to_string(),
1490                        ThemeStyleContent {
1491                            accents: vec![
1492                                AccentContent(Some("#ff0000".to_string())),
1493                                AccentContent(Some("#0000ff".to_string())),
1494                            ],
1495                            ..ThemeStyleContent::default()
1496                        },
1497                    )]);
1498                });
1499            });
1500        });
1501        cx.executor().advance_clock(Duration::from_millis(100));
1502        cx.executor().run_until_parked();
1503        let editor_snapshot = editor
1504            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1505            .unwrap();
1506        assert_eq!(
1507            indoc! {r#"
1508
1509
1510{«1[]1»fn main«1()1» «1{«2{«1()1»}2»}1»
1511
1512
1513mod foo «1{
1514    fn process_data_1«2()2» «2{
1515        let map: Option«1<Vec«2<«1()1»>2»>1» = None;
1516        // a
1517        // b
1518        // c
1519
1520    fn process_data_2«2()2» «2{
1521        let other_map: Option«1<Vec«2<«1()1»>2»>1» = None;
1522    }2»
1523}1»
1524
15251 hsla(0.00, 100.00%, 78.12%, 1.00)
15262 hsla(240.00, 100.00%, 82.81%, 1.00)
1527"#,},
1528            &editor_bracket_colors_markup(&editor_snapshot),
1529            "After updating theme accents, the editor should update the bracket coloring"
1530        );
1531    }
1532
1533    #[gpui::test]
1534    async fn test_multi_buffer_close_excerpts(cx: &mut gpui::TestAppContext) {
1535        let comment_lines = 5;
1536
1537        init_test(cx, |language_settings| {
1538            language_settings.defaults.colorize_brackets = Some(true);
1539        });
1540        let fs = FakeFs::new(cx.background_executor.clone());
1541        fs.insert_tree(
1542            path!("/a"),
1543            json!({
1544                "lib.rs": separate_with_comment_lines(
1545                    indoc! {r#"
1546    fn process_data_1() {
1547        let map: Option<Vec<()>> = None;
1548    }
1549    "#},
1550                    indoc! {r#"
1551    fn process_data_2() {
1552        let other_map: Option<Vec<()>> = None;
1553    }
1554    "#},
1555                    comment_lines,
1556                )
1557            }),
1558        )
1559        .await;
1560
1561        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1562        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1563        language_registry.add(rust_lang());
1564
1565        let buffer_1 = project
1566            .update(cx, |project, cx| {
1567                project.open_local_buffer(path!("/a/lib.rs"), cx)
1568            })
1569            .await
1570            .unwrap();
1571
1572        let second_excerpt_start = buffer_1.read_with(cx, |buffer, _| {
1573            let text = buffer.text();
1574            text.lines()
1575                .enumerate()
1576                .find(|(_, line)| line.contains("process_data_2"))
1577                .map(|(row, _)| row as u32)
1578                .unwrap()
1579        });
1580
1581        let multi_buffer = cx.new(|cx| {
1582            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1583            multi_buffer.set_excerpts_for_path(
1584                PathKey::sorted(0),
1585                buffer_1.clone(),
1586                [
1587                    Point::new(0, 0)..Point::new(3, 0),
1588                    Point::new(second_excerpt_start, 0)..Point::new(second_excerpt_start + 3, 0),
1589                ],
1590                0,
1591                cx,
1592            );
1593            multi_buffer
1594        });
1595
1596        let editor = cx.add_window(|window, cx| {
1597            Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1598        });
1599        cx.executor().advance_clock(Duration::from_millis(100));
1600        cx.executor().run_until_parked();
1601
1602        let editor_snapshot = editor
1603            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1604            .unwrap();
1605        assert_eq!(
1606            concat!(
1607                "\n",
1608                "\n",
1609                "fn process_data_1\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1610                "    let map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1611                "}1\u{00bb}\n",
1612                "\n",
1613                "\n",
1614                "fn process_data_2\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1615                "    let other_map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1616                "}1\u{00bb}\n",
1617                "\n",
1618                "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n",
1619                "2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
1620                "3 hsla(286.00, 51.00%, 75.25%, 1.00)\n",
1621                "4 hsla(187.00, 47.00%, 59.22%, 1.00)\n",
1622            ),
1623            &editor_bracket_colors_markup(&editor_snapshot),
1624            "Two close excerpts from the same buffer (within same tree-sitter chunk) should both have bracket colors"
1625        );
1626    }
1627
1628    #[gpui::test]
1629    // reproduction of #47846
1630    async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
1631        init_test(cx, |language_settings| {
1632            language_settings.defaults.colorize_brackets = Some(true);
1633        });
1634        let mut cx = EditorLspTestContext::new(
1635            Arc::into_inner(rust_lang()).unwrap(),
1636            lsp::ServerCapabilities::default(),
1637            cx,
1638        )
1639        .await;
1640
1641        // Generate a large function body. When folded, this collapses
1642        // to a single display line, making small_function visible on screen.
1643        let mut big_body = String::new();
1644        for i in 0..700 {
1645            big_body.push_str(&format!("    let var_{i:04} = ({i});\n"));
1646        }
1647        let source = format!(
1648            "ˇfn big_function() {{\n{big_body}}}\n\nfn small_function() {{\n    let x = (1, (2, 3));\n}}\n"
1649        );
1650
1651        cx.set_state(&source);
1652        cx.executor().advance_clock(Duration::from_millis(100));
1653        cx.executor().run_until_parked();
1654
1655        cx.update_editor(|editor, window, cx| {
1656            editor.fold_ranges(
1657                vec![Point::new(0, 0)..Point::new(701, 1)],
1658                false,
1659                window,
1660                cx,
1661            );
1662        });
1663        cx.executor().advance_clock(Duration::from_millis(100));
1664        cx.executor().run_until_parked();
1665
1666        assert_eq!(
1667            indoc! {r#"
1668⋯1»
1669
1670fn small_function«1()1» «1{
1671    let x = «2(1, «3(2, 3)3»)2»;
1672}1»
1673
16741 hsla(207.80, 16.20%, 69.19%, 1.00)
16752 hsla(29.00, 54.00%, 65.88%, 1.00)
16763 hsla(286.00, 51.00%, 75.25%, 1.00)
1677"#,},
1678            bracket_colors_markup(&mut cx),
1679        );
1680    }
1681
1682    fn separate_with_comment_lines(head: &str, tail: &str, comment_lines: usize) -> String {
1683        let mut result = head.to_string();
1684        result.push('\n');
1685        for _ in 0..comment_lines {
1686            result.push_str("// padding padding padding\n");
1687        }
1688        result.push_str(tail);
1689        result
1690    }
1691
1692    fn bracket_colors_markup(cx: &mut EditorTestContext) -> String {
1693        cx.update_editor(|editor, window, cx| {
1694            editor_bracket_colors_markup(&editor.snapshot(window, cx))
1695        })
1696    }
1697
1698    fn editor_bracket_colors_markup(snapshot: &EditorSnapshot) -> String {
1699        fn display_point_to_offset(text: &str, point: DisplayPoint) -> usize {
1700            let mut offset = 0;
1701            for (row_idx, line) in text.lines().enumerate() {
1702                if row_idx < point.row().0 as usize {
1703                    offset += line.len() + 1; // +1 for newline
1704                } else {
1705                    offset += point.column() as usize;
1706                    break;
1707                }
1708            }
1709            offset
1710        }
1711
1712        let actual_ranges = snapshot
1713            .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)));
1714        let editor_text = snapshot.text();
1715
1716        let mut next_index = 1;
1717        let mut color_to_index = HashMap::default();
1718        let mut annotations = Vec::new();
1719        for (color, range) in &actual_ranges {
1720            let color_index = *color_to_index
1721                .entry(*color)
1722                .or_insert_with(|| post_inc(&mut next_index));
1723            let start = snapshot.point_to_display_point(range.start, Bias::Left);
1724            let end = snapshot.point_to_display_point(range.end, Bias::Right);
1725            let start_offset = display_point_to_offset(&editor_text, start);
1726            let end_offset = display_point_to_offset(&editor_text, end);
1727            let bracket_text = &editor_text[start_offset..end_offset];
1728            let bracket_char = bracket_text.chars().next().unwrap();
1729
1730            if matches!(bracket_char, '{' | '[' | '(' | '<') {
1731                annotations.push((start_offset, format!("«{color_index}")));
1732            } else {
1733                annotations.push((end_offset, format!("{color_index}»")));
1734            }
1735        }
1736
1737        annotations.sort_by(|(pos_a, text_a), (pos_b, text_b)| {
1738            pos_a.cmp(pos_b).reverse().then_with(|| {
1739                let a_is_opening = text_a.starts_with('«');
1740                let b_is_opening = text_b.starts_with('«');
1741                match (a_is_opening, b_is_opening) {
1742                    (true, false) => cmp::Ordering::Less,
1743                    (false, true) => cmp::Ordering::Greater,
1744                    _ => cmp::Ordering::Equal,
1745                }
1746            })
1747        });
1748        annotations.dedup();
1749
1750        let mut markup = editor_text;
1751        for (offset, text) in annotations {
1752            markup.insert_str(offset, &text);
1753        }
1754
1755        markup.push_str("\n");
1756        for (index, color) in color_to_index
1757            .iter()
1758            .map(|(color, index)| (*index, *color))
1759            .sorted_by_key(|(index, _)| *index)
1760        {
1761            markup.push_str(&format!("{index} {color}\n"));
1762        }
1763
1764        markup
1765    }
1766
1767    fn filter_bracket_relevant_lines(markup: &str) -> String {
1768        markup
1769            .lines()
1770            .filter(|line| {
1771                let trimmed = line.trim();
1772                !trimmed.is_empty()
1773                    && !trimmed.starts_with("//")
1774                    && !trimmed.starts_with("hsla(")
1775                    && !trimmed.chars().next().is_some_and(|c| c.is_ascii_digit())
1776            })
1777            .collect::<Vec<_>>()
1778            .join("\n")
1779    }
1780}