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;
   8use collections::HashMap;
   9use gpui::{Context, HighlightStyle};
  10use itertools::Itertools;
  11use language::language_settings;
  12use multi_buffer::{Anchor, ExcerptId};
  13use ui::{ActiveTheme, utils::ensure_minimum_contrast};
  14
  15struct ColorizedBracketsHighlight;
  16
  17impl Editor {
  18    pub(crate) fn colorize_brackets(&mut self, invalidate: bool, cx: &mut Context<Editor>) {
  19        if !self.mode.is_full() {
  20            return;
  21        }
  22
  23        if invalidate {
  24            self.fetched_tree_sitter_chunks.clear();
  25        }
  26
  27        let accents_count = cx.theme().accents().0.len();
  28        let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
  29        let all_excerpts = self.buffer().read(cx).excerpt_ids();
  30        let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
  31                                       text_anchors: [text::Anchor; 4]|
  32         -> Option<[Option<_>; 4]> {
  33            multi_buffer_snapshot
  34                .anchors_in_excerpt(current_excerpt, text_anchors)
  35                .or_else(|| {
  36                    all_excerpts
  37                        .iter()
  38                        .filter(|&&excerpt_id| excerpt_id != current_excerpt)
  39                        .find_map(|&excerpt_id| {
  40                            multi_buffer_snapshot.anchors_in_excerpt(excerpt_id, text_anchors)
  41                        })
  42                })?
  43                .collect_array()
  44        };
  45
  46        let bracket_matches_by_accent = self.visible_excerpts(false, cx).into_iter().fold(
  47            HashMap::default(),
  48            |mut acc, (excerpt_id, (buffer, _, buffer_range))| {
  49                let buffer_snapshot = buffer.read(cx).snapshot();
  50                if language_settings::language_settings(
  51                    buffer_snapshot.language().map(|language| language.name()),
  52                    buffer_snapshot.file(),
  53                    cx,
  54                )
  55                .colorize_brackets
  56                {
  57                    let fetched_chunks = self
  58                        .fetched_tree_sitter_chunks
  59                        .entry(excerpt_id)
  60                        .or_default();
  61
  62                    let brackets_by_accent = buffer_snapshot
  63                        .fetch_bracket_ranges(
  64                            buffer_range.start..buffer_range.end,
  65                            Some(fetched_chunks),
  66                        )
  67                        .into_iter()
  68                        .flat_map(|(chunk_range, pairs)| {
  69                            if fetched_chunks.insert(chunk_range) {
  70                                pairs
  71                            } else {
  72                                Vec::new()
  73                            }
  74                        })
  75                        .filter_map(|pair| {
  76                            let color_index = pair.color_index?;
  77
  78                            let buffer_open_range = buffer_snapshot
  79                                .anchor_before(pair.open_range.start)
  80                                ..buffer_snapshot.anchor_after(pair.open_range.end);
  81                            let buffer_close_range = buffer_snapshot
  82                                .anchor_before(pair.close_range.start)
  83                                ..buffer_snapshot.anchor_after(pair.close_range.end);
  84                            let [
  85                                buffer_open_range_start,
  86                                buffer_open_range_end,
  87                                buffer_close_range_start,
  88                                buffer_close_range_end,
  89                            ] = anchors_in_multi_buffer(
  90                                excerpt_id,
  91                                [
  92                                    buffer_open_range.start,
  93                                    buffer_open_range.end,
  94                                    buffer_close_range.start,
  95                                    buffer_close_range.end,
  96                                ],
  97                            )?;
  98                            let multi_buffer_open_range =
  99                                buffer_open_range_start.zip(buffer_open_range_end);
 100                            let multi_buffer_close_range =
 101                                buffer_close_range_start.zip(buffer_close_range_end);
 102
 103                            let mut ranges = Vec::with_capacity(2);
 104                            if let Some((open_start, open_end)) = multi_buffer_open_range {
 105                                ranges.push(open_start..open_end);
 106                            }
 107                            if let Some((close_start, close_end)) = multi_buffer_close_range {
 108                                ranges.push(close_start..close_end);
 109                            }
 110                            if ranges.is_empty() {
 111                                None
 112                            } else {
 113                                Some((color_index % accents_count, ranges))
 114                            }
 115                        });
 116
 117                    for (accent_number, new_ranges) in brackets_by_accent {
 118                        let ranges = acc
 119                            .entry(accent_number)
 120                            .or_insert_with(Vec::<Range<Anchor>>::new);
 121
 122                        for new_range in new_ranges {
 123                            let i = ranges
 124                                .binary_search_by(|probe| {
 125                                    probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
 126                                })
 127                                .unwrap_or_else(|i| i);
 128                            ranges.insert(i, new_range);
 129                        }
 130                    }
 131                }
 132
 133                acc
 134            },
 135        );
 136
 137        if invalidate {
 138            self.clear_highlights::<ColorizedBracketsHighlight>(cx);
 139        }
 140
 141        let editor_background = cx.theme().colors().editor_background;
 142        for (accent_number, bracket_highlights) in bracket_matches_by_accent {
 143            let bracket_color = cx.theme().accents().color_for_index(accent_number as u32);
 144            let adjusted_color = ensure_minimum_contrast(bracket_color, editor_background, 55.0);
 145            let style = HighlightStyle {
 146                color: Some(adjusted_color),
 147                ..HighlightStyle::default()
 148            };
 149
 150            self.highlight_text_key::<ColorizedBracketsHighlight>(
 151                accent_number,
 152                bracket_highlights,
 153                style,
 154                true,
 155                cx,
 156            );
 157        }
 158    }
 159}
 160
 161#[cfg(test)]
 162mod tests {
 163    use std::{cmp, sync::Arc, time::Duration};
 164
 165    use super::*;
 166    use crate::{
 167        DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
 168        display_map::{DisplayRow, ToDisplayPoint},
 169        editor_tests::init_test,
 170        test::{
 171            editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
 172        },
 173    };
 174    use collections::HashSet;
 175    use fs::FakeFs;
 176    use gpui::{AppContext as _, UpdateGlobal as _};
 177    use indoc::indoc;
 178    use itertools::Itertools;
 179    use language::{Capability, markdown_lang};
 180    use languages::rust_lang;
 181    use multi_buffer::{ExcerptRange, MultiBuffer};
 182    use pretty_assertions::assert_eq;
 183    use project::Project;
 184    use rope::Point;
 185    use serde_json::json;
 186    use settings::{AccentContent, SettingsStore};
 187    use text::{Bias, OffsetRangeExt, ToOffset};
 188    use theme::ThemeStyleContent;
 189
 190    use util::{path, post_inc};
 191
 192    #[gpui::test]
 193    async fn test_basic_bracket_colorization(cx: &mut gpui::TestAppContext) {
 194        init_test(cx, |language_settings| {
 195            language_settings.defaults.colorize_brackets = Some(true);
 196        });
 197        let mut cx = EditorLspTestContext::new(
 198            Arc::into_inner(rust_lang()).unwrap(),
 199            lsp::ServerCapabilities::default(),
 200            cx,
 201        )
 202        .await;
 203
 204        cx.set_state(indoc! {r#"ˇuse std::{collections::HashMap, future::Future};
 205
 206fn main() {
 207    let a = one((), { () }, ());
 208    println!("{a}");
 209    println!("{a}");
 210    for i in 0..a {
 211        println!("{i}");
 212    }
 213
 214    let b = {
 215        {
 216            {
 217                [([([([([([([([([([((), ())])])])])])])])])])]
 218            }
 219        }
 220    };
 221}
 222
 223#[rustfmt::skip]
 224fn one(a: (), (): (), c: ()) -> usize { 1 }
 225
 226fn two<T>(a: HashMap<String, Vec<Option<T>>>) -> usize
 227where
 228    T: Future<Output = HashMap<String, Vec<Option<Box<()>>>>>,
 229{
 230    2
 231}
 232"#});
 233        cx.executor().advance_clock(Duration::from_millis(100));
 234        cx.executor().run_until_parked();
 235
 236        assert_eq!(
 237            r#"use std::«1{collections::HashMap, future::Future}1»;
 238
 239fn main«1()1» «1{
 240    let a = one«2(«3()3», «3{ «4()4» }3», «3()3»)2»;
 241    println!«2("{a}")2»;
 242    println!«2("{a}")2»;
 243    for i in 0..a «2{
 244        println!«3("{i}")3»;
 245    }2»
 246
 247    let b = «2{
 248        «3{
 249            «4{
 250                «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»
 251            }4»
 252        }3»
 253    }2»;
 254}1»
 255
 256#«1[rustfmt::skip]1»
 257fn one«1(a: «2()2», «2()2»: «2()2», c: «2()2»)1» -> usize «1{ 1 }1»
 258
 259fn two«1<T>1»«1(a: HashMap«2<String, Vec«3<Option«4<T>4»>3»>2»)1» -> usize
 260where
 261    T: Future«1<Output = HashMap«2<String, Vec«3<Option«4<Box«5<«6()6»>5»>4»>3»>2»>1»,
 262«1{
 263    2
 264}1»
 265
 2661 hsla(207.80, 16.20%, 69.19%, 1.00)
 2672 hsla(29.00, 54.00%, 65.88%, 1.00)
 2683 hsla(286.00, 51.00%, 75.25%, 1.00)
 2694 hsla(187.00, 47.00%, 59.22%, 1.00)
 2705 hsla(355.00, 65.00%, 75.94%, 1.00)
 2716 hsla(95.00, 38.00%, 62.00%, 1.00)
 2727 hsla(39.00, 67.00%, 69.00%, 1.00)
 273"#,
 274            &bracket_colors_markup(&mut cx),
 275            "All brackets should be colored based on their depth"
 276        );
 277    }
 278
 279    #[gpui::test]
 280    async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
 281        init_test(cx, |language_settings| {
 282            language_settings.defaults.colorize_brackets = Some(true);
 283        });
 284        let editor = cx.add_window(|window, cx| {
 285            let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
 286            multi_buffer.update(cx, |multi_buffer, cx| {
 287                multi_buffer
 288                    .as_singleton()
 289                    .unwrap()
 290                    .update(cx, |buffer, cx| {
 291                        buffer.set_language(Some(rust_lang()), cx);
 292                    });
 293            });
 294            Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 295        });
 296
 297        cx.executor().advance_clock(Duration::from_millis(100));
 298        cx.executor().run_until_parked();
 299
 300        assert_eq!(
 301            "fn main«1()1» «1{}1»
 3021 hsla(207.80, 16.20%, 69.19%, 1.00)
 303",
 304            editor
 305                .update(cx, |editor, window, cx| {
 306                    editor_bracket_colors_markup(&editor.snapshot(window, cx))
 307                })
 308                .unwrap(),
 309            "File-less buffer should still have its brackets colorized"
 310        );
 311    }
 312
 313    #[gpui::test]
 314    async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
 315        init_test(cx, |language_settings| {
 316            language_settings.defaults.colorize_brackets = Some(true);
 317        });
 318        let mut cx = EditorLspTestContext::new(
 319            Arc::into_inner(markdown_lang()).unwrap(),
 320            lsp::ServerCapabilities::default(),
 321            cx,
 322        )
 323        .await;
 324
 325        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)"#});
 326        cx.executor().advance_clock(Duration::from_millis(100));
 327        cx.executor().run_until_parked();
 328
 329        assert_eq!(
 330            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»
 3311 hsla(207.80, 16.20%, 69.19%, 1.00)
 332"#,
 333            &bracket_colors_markup(&mut cx),
 334            "All markdown brackets should be colored based on their depth"
 335        );
 336
 337        cx.set_state(indoc! {r#"ˇ{{}}"#});
 338        cx.executor().advance_clock(Duration::from_millis(100));
 339        cx.executor().run_until_parked();
 340
 341        assert_eq!(
 342            r#"«1{«2{}2»}1»
 3431 hsla(207.80, 16.20%, 69.19%, 1.00)
 3442 hsla(29.00, 54.00%, 65.88%, 1.00)
 345"#,
 346            &bracket_colors_markup(&mut cx),
 347            "All markdown brackets should be colored based on their depth, again"
 348        );
 349    }
 350
 351    #[gpui::test]
 352    async fn test_markdown_brackets_in_multiple_hunks(cx: &mut gpui::TestAppContext) {
 353        init_test(cx, |language_settings| {
 354            language_settings.defaults.colorize_brackets = Some(true);
 355        });
 356        let mut cx = EditorLspTestContext::new(
 357            Arc::into_inner(markdown_lang()).unwrap(),
 358            lsp::ServerCapabilities::default(),
 359            cx,
 360        )
 361        .await;
 362
 363        let rows = 100;
 364        let footer = "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n";
 365
 366        let simple_brackets = (0..rows).map(|_| "ˇ[]\n").collect::<String>();
 367        let simple_brackets_highlights = (0..rows).map(|_| "«1[]1»\n").collect::<String>();
 368        cx.set_state(&simple_brackets);
 369        cx.update_editor(|editor, window, cx| {
 370            editor.move_to_end(&MoveToEnd, window, cx);
 371        });
 372        cx.executor().advance_clock(Duration::from_millis(100));
 373        cx.executor().run_until_parked();
 374        assert_eq!(
 375            format!("{simple_brackets_highlights}\n{footer}"),
 376            bracket_colors_markup(&mut cx),
 377            "Simple bracket pairs should be colored"
 378        );
 379
 380        let paired_brackets = (0..rows).map(|_| "ˇ[]()\n").collect::<String>();
 381        let paired_brackets_highlights = (0..rows).map(|_| "«1[]1»«1()1»\n").collect::<String>();
 382        cx.set_state(&paired_brackets);
 383        // Wait for reparse to complete after content change
 384        cx.executor().advance_clock(Duration::from_millis(100));
 385        cx.executor().run_until_parked();
 386        cx.update_editor(|editor, _, cx| {
 387            // Force invalidation of bracket cache after reparse
 388            editor.colorize_brackets(true, cx);
 389        });
 390        // Scroll to beginning to fetch first chunks
 391        cx.update_editor(|editor, window, cx| {
 392            editor.move_to_beginning(&MoveToBeginning, window, cx);
 393        });
 394        cx.executor().advance_clock(Duration::from_millis(100));
 395        cx.executor().run_until_parked();
 396        // Scroll to end to fetch remaining chunks
 397        cx.update_editor(|editor, window, cx| {
 398            editor.move_to_end(&MoveToEnd, window, cx);
 399        });
 400        cx.executor().advance_clock(Duration::from_millis(100));
 401        cx.executor().run_until_parked();
 402        assert_eq!(
 403            format!("{paired_brackets_highlights}\n{footer}"),
 404            bracket_colors_markup(&mut cx),
 405            "Paired bracket pairs should be colored"
 406        );
 407    }
 408
 409    #[gpui::test]
 410    async fn test_bracket_colorization_after_language_swap(cx: &mut gpui::TestAppContext) {
 411        init_test(cx, |language_settings| {
 412            language_settings.defaults.colorize_brackets = Some(true);
 413        });
 414
 415        let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
 416        language_registry.add(markdown_lang());
 417        language_registry.add(rust_lang());
 418
 419        let mut cx = EditorTestContext::new(cx).await;
 420        cx.update_buffer(|buffer, cx| {
 421            buffer.set_language_registry(language_registry.clone());
 422            buffer.set_language(Some(markdown_lang()), cx);
 423        });
 424
 425        cx.set_state(indoc! {r#"
 426            fn main() {
 427                let v: Vec<Stringˇ> = vec![];
 428            }
 429        "#});
 430        cx.executor().advance_clock(Duration::from_millis(100));
 431        cx.executor().run_until_parked();
 432
 433        assert_eq!(
 434            r#"fn main«1()1» «1{
 435    let v: Vec<String> = vec!«2[]2»;
 436}1»
 437
 4381 hsla(207.80, 16.20%, 69.19%, 1.00)
 4392 hsla(29.00, 54.00%, 65.88%, 1.00)
 440"#,
 441            &bracket_colors_markup(&mut cx),
 442            "Markdown does not colorize <> brackets"
 443        );
 444
 445        cx.update_buffer(|buffer, cx| {
 446            buffer.set_language(Some(rust_lang()), cx);
 447        });
 448        cx.executor().advance_clock(Duration::from_millis(100));
 449        cx.executor().run_until_parked();
 450
 451        assert_eq!(
 452            r#"fn main«1()1» «1{
 453    let v: Vec«2<String>2» = vec!«2[]2»;
 454}1»
 455
 4561 hsla(207.80, 16.20%, 69.19%, 1.00)
 4572 hsla(29.00, 54.00%, 65.88%, 1.00)
 458"#,
 459            &bracket_colors_markup(&mut cx),
 460            "After switching to Rust, <> brackets are now colorized"
 461        );
 462    }
 463
 464    #[gpui::test]
 465    async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
 466        init_test(cx, |language_settings| {
 467            language_settings.defaults.colorize_brackets = Some(true);
 468        });
 469        let mut cx = EditorLspTestContext::new(
 470            Arc::into_inner(rust_lang()).unwrap(),
 471            lsp::ServerCapabilities::default(),
 472            cx,
 473        )
 474        .await;
 475
 476        cx.set_state(indoc! {r#"
 477struct Foo<'a, T> {
 478    data: Vec<Option<&'a T>>,
 479}
 480
 481fn process_data() {
 482    let map:ˇ
 483}
 484"#});
 485
 486        cx.update_editor(|editor, window, cx| {
 487            editor.handle_input(" Result<", window, cx);
 488        });
 489        cx.executor().advance_clock(Duration::from_millis(100));
 490        cx.executor().run_until_parked();
 491        assert_eq!(
 492            indoc! {r#"
 493struct Foo«1<'a, T>1» «1{
 494    data: Vec«2<Option«3<&'a T>3»>2»,
 495}1»
 496
 497fn process_data«1()1» «1{
 498    let map: Result<
 499}1»
 500
 5011 hsla(207.80, 16.20%, 69.19%, 1.00)
 5022 hsla(29.00, 54.00%, 65.88%, 1.00)
 5033 hsla(286.00, 51.00%, 75.25%, 1.00)
 504"#},
 505            &bracket_colors_markup(&mut cx),
 506            "Brackets without pairs should be ignored and not colored"
 507        );
 508
 509        cx.update_editor(|editor, window, cx| {
 510            editor.handle_input("Option<Foo<'_, ()", window, cx);
 511        });
 512        cx.executor().advance_clock(Duration::from_millis(100));
 513        cx.executor().run_until_parked();
 514        assert_eq!(
 515            indoc! {r#"
 516struct Foo«1<'a, T>1» «1{
 517    data: Vec«2<Option«3<&'a T>3»>2»,
 518}1»
 519
 520fn process_data«1()1» «1{
 521    let map: Result<Option<Foo<'_, «2()2»
 522}1»
 523
 5241 hsla(207.80, 16.20%, 69.19%, 1.00)
 5252 hsla(29.00, 54.00%, 65.88%, 1.00)
 5263 hsla(286.00, 51.00%, 75.25%, 1.00)
 527"#},
 528            &bracket_colors_markup(&mut cx),
 529        );
 530
 531        cx.update_editor(|editor, window, cx| {
 532            editor.handle_input(">", window, cx);
 533        });
 534        cx.executor().advance_clock(Duration::from_millis(100));
 535        cx.executor().run_until_parked();
 536        assert_eq!(
 537            indoc! {r#"
 538struct Foo«1<'a, T>1» «1{
 539    data: Vec«2<Option«3<&'a T>3»>2»,
 540}1»
 541
 542fn process_data«1()1» «1{
 543    let map: Result<Option<Foo«2<'_, «3()3»>2»
 544}1»
 545
 5461 hsla(207.80, 16.20%, 69.19%, 1.00)
 5472 hsla(29.00, 54.00%, 65.88%, 1.00)
 5483 hsla(286.00, 51.00%, 75.25%, 1.00)
 549"#},
 550            &bracket_colors_markup(&mut cx),
 551            "When brackets start to get closed, inner brackets are re-colored based on their depth"
 552        );
 553
 554        cx.update_editor(|editor, window, cx| {
 555            editor.handle_input(">", window, cx);
 556        });
 557        cx.executor().advance_clock(Duration::from_millis(100));
 558        cx.executor().run_until_parked();
 559        assert_eq!(
 560            indoc! {r#"
 561struct Foo«1<'a, T>1» «1{
 562    data: Vec«2<Option«3<&'a T>3»>2»,
 563}1»
 564
 565fn process_data«1()1» «1{
 566    let map: Result<Option«2<Foo«3<'_, «4()4»>3»>2»
 567}1»
 568
 5691 hsla(207.80, 16.20%, 69.19%, 1.00)
 5702 hsla(29.00, 54.00%, 65.88%, 1.00)
 5713 hsla(286.00, 51.00%, 75.25%, 1.00)
 5724 hsla(187.00, 47.00%, 59.22%, 1.00)
 573"#},
 574            &bracket_colors_markup(&mut cx),
 575        );
 576
 577        cx.update_editor(|editor, window, cx| {
 578            editor.handle_input(", ()> = unimplemented!();", window, cx);
 579        });
 580        cx.executor().advance_clock(Duration::from_millis(100));
 581        cx.executor().run_until_parked();
 582        assert_eq!(
 583            indoc! {r#"
 584struct Foo«1<'a, T>1» «1{
 585    data: Vec«2<Option«3<&'a T>3»>2»,
 586}1»
 587
 588fn process_data«1()1» «1{
 589    let map: Result«2<Option«3<Foo«4<'_, «5()5»>4»>3», «3()3»>2» = unimplemented!«2()2»;
 590}1»
 591
 5921 hsla(207.80, 16.20%, 69.19%, 1.00)
 5932 hsla(29.00, 54.00%, 65.88%, 1.00)
 5943 hsla(286.00, 51.00%, 75.25%, 1.00)
 5954 hsla(187.00, 47.00%, 59.22%, 1.00)
 5965 hsla(355.00, 65.00%, 75.94%, 1.00)
 597"#},
 598            &bracket_colors_markup(&mut cx),
 599        );
 600    }
 601
 602    #[gpui::test]
 603    async fn test_bracket_colorization_chunks(cx: &mut gpui::TestAppContext) {
 604        let comment_lines = 100;
 605
 606        init_test(cx, |language_settings| {
 607            language_settings.defaults.colorize_brackets = Some(true);
 608        });
 609        let mut cx = EditorLspTestContext::new(
 610            Arc::into_inner(rust_lang()).unwrap(),
 611            lsp::ServerCapabilities::default(),
 612            cx,
 613        )
 614        .await;
 615
 616        cx.set_state(&separate_with_comment_lines(
 617            indoc! {r#"
 618mod foo {
 619    ˇfn process_data_1() {
 620        let map: Option<Vec<()>> = None;
 621    }
 622"#},
 623            indoc! {r#"
 624    fn process_data_2() {
 625        let map: Option<Vec<()>> = None;
 626    }
 627}
 628"#},
 629            comment_lines,
 630        ));
 631
 632        cx.executor().advance_clock(Duration::from_millis(100));
 633        cx.executor().run_until_parked();
 634        assert_eq!(
 635            &separate_with_comment_lines(
 636                indoc! {r#"
 637mod foo «1{
 638    fn process_data_1«2()2» «2{
 639        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 640    }2»
 641"#},
 642                indoc! {r#"
 643    fn process_data_2() {
 644        let map: Option<Vec<()>> = None;
 645    }
 646}1»
 647
 6481 hsla(207.80, 16.20%, 69.19%, 1.00)
 6492 hsla(29.00, 54.00%, 65.88%, 1.00)
 6503 hsla(286.00, 51.00%, 75.25%, 1.00)
 6514 hsla(187.00, 47.00%, 59.22%, 1.00)
 6525 hsla(355.00, 65.00%, 75.94%, 1.00)
 653"#},
 654                comment_lines,
 655            ),
 656            &bracket_colors_markup(&mut cx),
 657            "First, the only visible chunk is getting the bracket highlights"
 658        );
 659
 660        cx.update_editor(|editor, window, cx| {
 661            editor.move_to_end(&MoveToEnd, window, cx);
 662            editor.move_up(&MoveUp, window, cx);
 663        });
 664        cx.executor().advance_clock(Duration::from_millis(100));
 665        cx.executor().run_until_parked();
 666        assert_eq!(
 667            &separate_with_comment_lines(
 668                indoc! {r#"
 669mod foo «1{
 670    fn process_data_1«2()2» «2{
 671        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 672    }2»
 673"#},
 674                indoc! {r#"
 675    fn process_data_2«2()2» «2{
 676        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 677    }2»
 678}1»
 679
 6801 hsla(207.80, 16.20%, 69.19%, 1.00)
 6812 hsla(29.00, 54.00%, 65.88%, 1.00)
 6823 hsla(286.00, 51.00%, 75.25%, 1.00)
 6834 hsla(187.00, 47.00%, 59.22%, 1.00)
 6845 hsla(355.00, 65.00%, 75.94%, 1.00)
 685"#},
 686                comment_lines,
 687            ),
 688            &bracket_colors_markup(&mut cx),
 689            "After scrolling to the bottom, both chunks should have the highlights"
 690        );
 691
 692        cx.update_editor(|editor, window, cx| {
 693            editor.handle_input("{{}}}", window, cx);
 694        });
 695        cx.executor().advance_clock(Duration::from_millis(100));
 696        cx.executor().run_until_parked();
 697        assert_eq!(
 698            &separate_with_comment_lines(
 699                indoc! {r#"
 700mod foo «1{
 701    fn process_data_1() {
 702        let map: Option<Vec<()>> = None;
 703    }
 704"#},
 705                indoc! {r#"
 706    fn process_data_2«2()2» «2{
 707        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 708    }
 709    «3{«4{}4»}3»}2»}1»
 710
 7111 hsla(207.80, 16.20%, 69.19%, 1.00)
 7122 hsla(29.00, 54.00%, 65.88%, 1.00)
 7133 hsla(286.00, 51.00%, 75.25%, 1.00)
 7144 hsla(187.00, 47.00%, 59.22%, 1.00)
 7155 hsla(355.00, 65.00%, 75.94%, 1.00)
 716"#},
 717                comment_lines,
 718            ),
 719            &bracket_colors_markup(&mut cx),
 720            "First chunk's brackets are invalidated after an edit, and only 2nd (visible) chunk is re-colorized"
 721        );
 722
 723        cx.update_editor(|editor, window, cx| {
 724            editor.move_to_beginning(&MoveToBeginning, window, cx);
 725        });
 726        cx.executor().advance_clock(Duration::from_millis(100));
 727        cx.executor().run_until_parked();
 728        assert_eq!(
 729            &separate_with_comment_lines(
 730                indoc! {r#"
 731mod foo «1{
 732    fn process_data_1«2()2» «2{
 733        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 734    }2»
 735"#},
 736                indoc! {r#"
 737    fn process_data_2«2()2» «2{
 738        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 739    }
 740    «3{«4{}4»}3»}2»}1»
 741
 7421 hsla(207.80, 16.20%, 69.19%, 1.00)
 7432 hsla(29.00, 54.00%, 65.88%, 1.00)
 7443 hsla(286.00, 51.00%, 75.25%, 1.00)
 7454 hsla(187.00, 47.00%, 59.22%, 1.00)
 7465 hsla(355.00, 65.00%, 75.94%, 1.00)
 747"#},
 748                comment_lines,
 749            ),
 750            &bracket_colors_markup(&mut cx),
 751            "Scrolling back to top should re-colorize all chunks' brackets"
 752        );
 753
 754        cx.update(|_, cx| {
 755            SettingsStore::update_global(cx, |store, cx| {
 756                store.update_user_settings(cx, |settings| {
 757                    settings.project.all_languages.defaults.colorize_brackets = Some(false);
 758                });
 759            });
 760        });
 761        assert_eq!(
 762            &separate_with_comment_lines(
 763                indoc! {r#"
 764mod foo {
 765    fn process_data_1() {
 766        let map: Option<Vec<()>> = None;
 767    }
 768"#},
 769                r#"    fn process_data_2() {
 770        let map: Option<Vec<()>> = None;
 771    }
 772    {{}}}}
 773
 774"#,
 775                comment_lines,
 776            ),
 777            &bracket_colors_markup(&mut cx),
 778            "Turning bracket colorization off should remove all bracket colors"
 779        );
 780
 781        cx.update(|_, cx| {
 782            SettingsStore::update_global(cx, |store, cx| {
 783                store.update_user_settings(cx, |settings| {
 784                    settings.project.all_languages.defaults.colorize_brackets = Some(true);
 785                });
 786            });
 787        });
 788        assert_eq!(
 789            &separate_with_comment_lines(
 790                indoc! {r#"
 791mod foo «1{
 792    fn process_data_1«2()2» «2{
 793        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 794    }2»
 795"#},
 796                r#"    fn process_data_2() {
 797        let map: Option<Vec<()>> = None;
 798    }
 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            "Turning bracket colorization back on refreshes the visible excerpts' bracket colors"
 811        );
 812    }
 813
 814    #[gpui::test]
 815    async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
 816        init_test(cx, |language_settings| {
 817            language_settings.defaults.colorize_brackets = Some(true);
 818        });
 819        let mut cx = EditorLspTestContext::new(
 820            Arc::into_inner(rust_lang()).unwrap(),
 821            lsp::ServerCapabilities::default(),
 822            cx,
 823        )
 824        .await;
 825
 826        // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
 827        cx.set_state(indoc! {r# 828            pub(crate) fn inlay_hints(
 829                db: &RootDatabase,
 830                file_id: FileId,
 831                range_limit: Option<TextRange>,
 832                config: &InlayHintsConfig,
 833            ) -> Vec<InlayHint> {
 834                let _p = tracing::info_span!("inlay_hints").entered();
 835                let sema = Semantics::new(db);
 836                let file_id = sema
 837                    .attach_first_edition(file_id)
 838                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 839                let file = sema.parse(file_id);
 840                let file = file.syntax();
 841
 842                let mut acc = Vec::new();
 843
 844                let Some(scope) = sema.scope(file) else {
 845                    return acc;
 846                };
 847                let famous_defs = FamousDefs(&sema, scope.krate());
 848                let display_target = famous_defs.1.to_display_target(sema.db);
 849
 850                let ctx = &mut InlayHintCtx::default();
 851                let mut hints = |event| {
 852                    if let Some(node) = handle_event(ctx, event) {
 853                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 854                    }
 855                };
 856                let mut preorder = file.preorder();
 857                salsa::attach(sema.db, || {
 858                    while let Some(event) = preorder.next() {
 859                        if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
 860                        {
 861                            preorder.skip_subtree();
 862                            continue;
 863                        }
 864                        hints(event);
 865                    }
 866                });
 867                if let Some(range_limit) = range_limit {
 868                    acc.retain(|hint| range_limit.contains_range(hint.range));
 869                }
 870                acc
 871            }
 872
 873            #[derive(Default)]
 874            struct InlayHintCtx {
 875                lifetime_stacks: Vec<Vec<SmolStr>>,
 876                extern_block_parent: Option<ast::ExternBlock>,
 877            }
 878
 879            pub(crate) fn inlay_hints_resolve(
 880                db: &RootDatabase,
 881                file_id: FileId,
 882                resolve_range: TextRange,
 883                hash: u64,
 884                config: &InlayHintsConfig,
 885                hasher: impl Fn(&InlayHint) -> u64,
 886            ) -> Option<InlayHint> {
 887                let _p = tracing::info_span!("inlay_hints_resolve").entered();
 888                let sema = Semantics::new(db);
 889                let file_id = sema
 890                    .attach_first_edition(file_id)
 891                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 892                let file = sema.parse(file_id);
 893                let file = file.syntax();
 894
 895                let scope = sema.scope(file)?;
 896                let famous_defs = FamousDefs(&sema, scope.krate());
 897                let mut acc = Vec::new();
 898
 899                let display_target = famous_defs.1.to_display_target(sema.db);
 900
 901                let ctx = &mut InlayHintCtx::default();
 902                let mut hints = |event| {
 903                    if let Some(node) = handle_event(ctx, event) {
 904                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 905                    }
 906                };
 907
 908                let mut preorder = file.preorder();
 909                while let Some(event) = preorder.next() {
 910                    // This can miss some hints that require the parent of the range to calculate
 911                    if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
 912                    {
 913                        preorder.skip_subtree();
 914                        continue;
 915                    }
 916                    hints(event);
 917                }
 918                acc.into_iter().find(|hint| hasher(hint) == hash)
 919            }
 920
 921            fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
 922                match node {
 923                    WalkEvent::Enter(node) => {
 924                        if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
 925                            let params = node
 926                                .generic_param_list()
 927                                .map(|it| {
 928                                    it.lifetime_params()
 929                                        .filter_map(|it| {
 930                                            it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
 931                                        })
 932                                        .collect()
 933                                })
 934                                .unwrap_or_default();
 935                            ctx.lifetime_stacks.push(params);
 936                        }
 937                        if let Some(node) = ast::ExternBlock::cast(node.clone()) {
 938                            ctx.extern_block_parent = Some(node);
 939                        }
 940                        Some(node)
 941                    }
 942                    WalkEvent::Leave(n) => {
 943                        if ast::AnyHasGenericParams::can_cast(n.kind()) {
 944                            ctx.lifetime_stacks.pop();
 945                        }
 946                        if ast::ExternBlock::can_cast(n.kind()) {
 947                            ctx.extern_block_parent = None;
 948                        }
 949                        None
 950                    }
 951                }
 952            }
 953
 954            // At some point when our hir infra is fleshed out enough we should flip this and traverse the
 955            // HIR instead of the syntax tree.
 956            fn hints(
 957                hints: &mut Vec<InlayHint>,
 958                ctx: &mut InlayHintCtx,
 959                famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
 960                config: &InlayHintsConfig,
 961                file_id: EditionedFileId,
 962                display_target: DisplayTarget,
 963                node: SyntaxNode,
 964            ) {
 965                closing_brace::hints(
 966                    hints,
 967                    sema,
 968                    config,
 969                    display_target,
 970                    InRealFile { file_id, value: node.clone() },
 971                );
 972                if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
 973                    generic_param::hints(hints, famous_defs, config, any_has_generic_args);
 974                }
 975
 976                match_ast! {
 977                    match node {
 978                        ast::Expr(expr) => {
 979                            chaining::hints(hints, famous_defs, config, display_target, &expr);
 980                            adjustment::hints(hints, famous_defs, config, display_target, &expr);
 981                            match expr {
 982                                ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
 983                                ast::Expr::MethodCallExpr(it) => {
 984                                    param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
 985                                }
 986                                ast::Expr::ClosureExpr(it) => {
 987                                    closure_captures::hints(hints, famous_defs, config, it.clone());
 988                                    closure_ret::hints(hints, famous_defs, config, display_target, it)
 989                                },
 990                                ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
 991                                _ => Some(()),
 992                            }
 993                        },
 994                        ast::Pat(it) => {
 995                            binding_mode::hints(hints, famous_defs, config, &it);
 996                            match it {
 997                                ast::Pat::IdentPat(it) => {
 998                                    bind_pat::hints(hints, famous_defs, config, display_target, &it);
 999                                }
1000                                ast::Pat::RangePat(it) => {
1001                                    range_exclusive::hints(hints, famous_defs, config, it);
1002                                }
1003                                _ => {}
1004                            }
1005                            Some(())
1006                        },
1007                        ast::Item(it) => match it {
1008                            ast::Item::Fn(it) => {
1009                                implicit_drop::hints(hints, famous_defs, config, display_target, &it);
1010                                if let Some(extern_block) = &ctx.extern_block_parent {
1011                                    extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
1012                                }
1013                                lifetime::fn_hints(hints, ctx, famous_defs, config,  it)
1014                            },
1015                            ast::Item::Static(it) => {
1016                                if let Some(extern_block) = &ctx.extern_block_parent {
1017                                    extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
1018                                }
1019                                implicit_static::hints(hints, famous_defs, config,  Either::Left(it))
1020                            },
1021                            ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
1022                            ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
1023                            ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
1024                            _ => None,
1025                        },
1026                        // trait object type elisions
1027                        ast::Type(ty) => match ty {
1028                            ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config,  ptr),
1029                            ast::Type::PathType(path) => {
1030                                lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
1031                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
1032                                Some(())
1033                            },
1034                            ast::Type::DynTraitType(dyn_) => {
1035                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
1036                                Some(())
1037                            },
1038                            _ => Some(()),
1039                        },
1040                        ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config,  it),
1041                        _ => Some(()),
1042                    }
1043                };
1044            }
1045        "#});
1046        cx.executor().advance_clock(Duration::from_millis(100));
1047        cx.executor().run_until_parked();
1048
1049        let actual_ranges = cx.update_editor(|editor, window, cx| {
1050            editor
1051                .snapshot(window, cx)
1052                .all_text_highlight_ranges::<ColorizedBracketsHighlight>()
1053        });
1054
1055        let mut highlighted_brackets = HashMap::default();
1056        for (color, range) in actual_ranges.iter().cloned() {
1057            highlighted_brackets.insert(range, color);
1058        }
1059
1060        let last_bracket = actual_ranges
1061            .iter()
1062            .max_by_key(|(_, p)| p.end.row)
1063            .unwrap()
1064            .clone();
1065
1066        cx.update_editor(|editor, window, cx| {
1067            let was_scrolled = editor.set_scroll_position(
1068                gpui::Point::new(0.0, last_bracket.1.end.row as f64 * 2.0),
1069                window,
1070                cx,
1071            );
1072            assert!(was_scrolled.0);
1073        });
1074        cx.executor().advance_clock(Duration::from_millis(100));
1075        cx.executor().run_until_parked();
1076
1077        let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
1078            editor
1079                .snapshot(window, cx)
1080                .all_text_highlight_ranges::<ColorizedBracketsHighlight>()
1081        });
1082        let new_last_bracket = ranges_after_scrolling
1083            .iter()
1084            .max_by_key(|(_, p)| p.end.row)
1085            .unwrap()
1086            .clone();
1087
1088        assert_ne!(
1089            last_bracket, new_last_bracket,
1090            "After scrolling down, we should have highlighted more brackets"
1091        );
1092
1093        cx.update_editor(|editor, window, cx| {
1094            let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
1095            assert!(was_scrolled.0);
1096        });
1097
1098        for _ in 0..200 {
1099            cx.update_editor(|editor, window, cx| {
1100                editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
1101            });
1102            cx.executor().advance_clock(Duration::from_millis(100));
1103            cx.executor().run_until_parked();
1104
1105            let colored_brackets = cx.update_editor(|editor, window, cx| {
1106                editor
1107                    .snapshot(window, cx)
1108                    .all_text_highlight_ranges::<ColorizedBracketsHighlight>()
1109            });
1110            for (color, range) in colored_brackets.clone() {
1111                assert!(
1112                    highlighted_brackets.entry(range).or_insert(color) == &color,
1113                    "Colors should stay consistent while scrolling!"
1114                );
1115            }
1116
1117            let snapshot = cx.update_editor(|editor, window, cx| editor.snapshot(window, cx));
1118            let scroll_position = snapshot.scroll_position();
1119            let visible_lines =
1120                cx.update_editor(|editor, _, _| editor.visible_line_count().unwrap());
1121            let visible_range = DisplayRow(scroll_position.y as u32)
1122                ..DisplayRow((scroll_position.y + visible_lines) as u32);
1123
1124            let current_highlighted_bracket_set: HashSet<Point> = HashSet::from_iter(
1125                colored_brackets
1126                    .iter()
1127                    .flat_map(|(_, range)| [range.start, range.end]),
1128            );
1129
1130            for highlight_range in highlighted_brackets.keys().filter(|bracket_range| {
1131                visible_range.contains(&bracket_range.start.to_display_point(&snapshot).row())
1132                    || visible_range.contains(&bracket_range.end.to_display_point(&snapshot).row())
1133            }) {
1134                assert!(
1135                    current_highlighted_bracket_set.contains(&highlight_range.start)
1136                        || current_highlighted_bracket_set.contains(&highlight_range.end),
1137                    "Should not lose highlights while scrolling in the visible range!"
1138                );
1139            }
1140
1141            let buffer_snapshot = snapshot.buffer().as_singleton().unwrap().2;
1142            for bracket_match in buffer_snapshot
1143                .fetch_bracket_ranges(
1144                    snapshot
1145                        .display_point_to_point(
1146                            DisplayPoint::new(visible_range.start, 0),
1147                            Bias::Left,
1148                        )
1149                        .to_offset(&buffer_snapshot)
1150                        ..snapshot
1151                            .display_point_to_point(
1152                                DisplayPoint::new(
1153                                    visible_range.end,
1154                                    snapshot.line_len(visible_range.end),
1155                                ),
1156                                Bias::Right,
1157                            )
1158                            .to_offset(&buffer_snapshot),
1159                    None,
1160                )
1161                .iter()
1162                .flat_map(|entry| entry.1)
1163                .filter(|bracket_match| bracket_match.color_index.is_some())
1164            {
1165                let start = bracket_match.open_range.to_point(buffer_snapshot);
1166                let end = bracket_match.close_range.to_point(buffer_snapshot);
1167                let start_bracket = colored_brackets.iter().find(|(_, range)| *range == start);
1168                assert!(
1169                    start_bracket.is_some(),
1170                    "Existing bracket start in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1171                    buffer_snapshot
1172                        .text_for_range(start.start..end.end)
1173                        .collect::<String>(),
1174                    start
1175                );
1176
1177                let end_bracket = colored_brackets.iter().find(|(_, range)| *range == end);
1178                assert!(
1179                    end_bracket.is_some(),
1180                    "Existing bracket end in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1181                    buffer_snapshot
1182                        .text_for_range(start.start..end.end)
1183                        .collect::<String>(),
1184                    start
1185                );
1186
1187                assert_eq!(
1188                    start_bracket.unwrap().0,
1189                    end_bracket.unwrap().0,
1190                    "Bracket pair should be highlighted the same color!"
1191                )
1192            }
1193        }
1194    }
1195
1196    #[gpui::test]
1197    async fn test_multi_buffer(cx: &mut gpui::TestAppContext) {
1198        let comment_lines = 100;
1199
1200        init_test(cx, |language_settings| {
1201            language_settings.defaults.colorize_brackets = Some(true);
1202        });
1203        let fs = FakeFs::new(cx.background_executor.clone());
1204        fs.insert_tree(
1205            path!("/a"),
1206            json!({
1207                "main.rs": "fn main() {{()}}",
1208                "lib.rs": separate_with_comment_lines(
1209                    indoc! {r#"
1210    mod foo {
1211        fn process_data_1() {
1212            let map: Option<Vec<()>> = None;
1213            // a
1214            // b
1215            // c
1216        }
1217    "#},
1218                    indoc! {r#"
1219        fn process_data_2() {
1220            let other_map: Option<Vec<()>> = None;
1221        }
1222    }
1223    "#},
1224                    comment_lines,
1225                )
1226            }),
1227        )
1228        .await;
1229
1230        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1231        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1232        language_registry.add(rust_lang());
1233
1234        let buffer_1 = project
1235            .update(cx, |project, cx| {
1236                project.open_local_buffer(path!("/a/lib.rs"), cx)
1237            })
1238            .await
1239            .unwrap();
1240        let buffer_2 = project
1241            .update(cx, |project, cx| {
1242                project.open_local_buffer(path!("/a/main.rs"), cx)
1243            })
1244            .await
1245            .unwrap();
1246
1247        let multi_buffer = cx.new(|cx| {
1248            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1249            multi_buffer.push_excerpts(
1250                buffer_2.clone(),
1251                [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
1252                cx,
1253            );
1254
1255            let excerpt_rows = 5;
1256            let rest_of_first_except_rows = 3;
1257            multi_buffer.push_excerpts(
1258                buffer_1.clone(),
1259                [
1260                    ExcerptRange::new(Point::new(0, 0)..Point::new(excerpt_rows, 0)),
1261                    ExcerptRange::new(
1262                        Point::new(
1263                            comment_lines as u32 + excerpt_rows + rest_of_first_except_rows,
1264                            0,
1265                        )
1266                            ..Point::new(
1267                                comment_lines as u32
1268                                    + excerpt_rows
1269                                    + rest_of_first_except_rows
1270                                    + excerpt_rows,
1271                                0,
1272                            ),
1273                    ),
1274                ],
1275                cx,
1276            );
1277            multi_buffer
1278        });
1279
1280        let editor = cx.add_window(|window, cx| {
1281            Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1282        });
1283        cx.executor().advance_clock(Duration::from_millis(100));
1284        cx.executor().run_until_parked();
1285
1286        let editor_snapshot = editor
1287            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1288            .unwrap();
1289        assert_eq!(
1290            indoc! {r#"
1291
1292
1293fn main«1()1» «1{«2{«3()3»}2»}1»
1294
1295
1296mod foo «1{
1297    fn process_data_1«2()2» «2{
1298        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1299        // a
1300        // b
1301
1302
1303    fn process_data_2«2()2» «2{
1304        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1305    }2»
1306}1»
1307
13081 hsla(207.80, 16.20%, 69.19%, 1.00)
13092 hsla(29.00, 54.00%, 65.88%, 1.00)
13103 hsla(286.00, 51.00%, 75.25%, 1.00)
13114 hsla(187.00, 47.00%, 59.22%, 1.00)
13125 hsla(355.00, 65.00%, 75.94%, 1.00)
1313"#,},
1314            &editor_bracket_colors_markup(&editor_snapshot),
1315            "Multi buffers should have their brackets colored even if no excerpts contain the bracket counterpart (after fn `process_data_2()`) \
1316or if the buffer pair spans across multiple excerpts (the one after `mod foo`)"
1317        );
1318
1319        editor
1320            .update(cx, |editor, window, cx| {
1321                editor.handle_input("{[]", window, cx);
1322            })
1323            .unwrap();
1324        cx.executor().advance_clock(Duration::from_millis(100));
1325        cx.executor().run_until_parked();
1326        let editor_snapshot = editor
1327            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1328            .unwrap();
1329        assert_eq!(
1330            indoc! {r#"
1331
1332
1333{«1[]1»fn main«1()1» «1{«2{«3()3»}2»}1»
1334
1335
1336mod foo «1{
1337    fn process_data_1«2()2» «2{
1338        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1339        // a
1340        // b
1341
1342
1343    fn process_data_2«2()2» «2{
1344        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1345    }2»
1346}1»
1347
13481 hsla(207.80, 16.20%, 69.19%, 1.00)
13492 hsla(29.00, 54.00%, 65.88%, 1.00)
13503 hsla(286.00, 51.00%, 75.25%, 1.00)
13514 hsla(187.00, 47.00%, 59.22%, 1.00)
13525 hsla(355.00, 65.00%, 75.94%, 1.00)
1353"#,},
1354            &editor_bracket_colors_markup(&editor_snapshot),
1355        );
1356
1357        cx.update(|cx| {
1358            let theme = cx.theme().name.clone();
1359            SettingsStore::update_global(cx, |store, cx| {
1360                store.update_user_settings(cx, |settings| {
1361                    settings.theme.theme_overrides = HashMap::from_iter([(
1362                        theme.to_string(),
1363                        ThemeStyleContent {
1364                            accents: vec![
1365                                AccentContent(Some("#ff0000".to_string())),
1366                                AccentContent(Some("#0000ff".to_string())),
1367                            ],
1368                            ..ThemeStyleContent::default()
1369                        },
1370                    )]);
1371                });
1372            });
1373        });
1374        cx.executor().advance_clock(Duration::from_millis(100));
1375        cx.executor().run_until_parked();
1376        let editor_snapshot = editor
1377            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1378            .unwrap();
1379        assert_eq!(
1380            indoc! {r#"
1381
1382
1383{«1[]1»fn main«1()1» «1{«2{«1()1»}2»}1»
1384
1385
1386mod foo «1{
1387    fn process_data_1«2()2» «2{
1388        let map: Option«1<Vec«2<«1()1»>2»>1» = None;
1389        // a
1390        // b
1391
1392
1393    fn process_data_2«2()2» «2{
1394        let other_map: Option«1<Vec«2<«1()1»>2»>1» = None;
1395    }2»
1396}1»
1397
13981 hsla(0.00, 100.00%, 78.12%, 1.00)
13992 hsla(240.00, 100.00%, 82.81%, 1.00)
1400"#,},
1401            &editor_bracket_colors_markup(&editor_snapshot),
1402            "After updating theme accents, the editor should update the bracket coloring"
1403        );
1404    }
1405
1406    fn separate_with_comment_lines(head: &str, tail: &str, comment_lines: usize) -> String {
1407        let mut result = head.to_string();
1408        result.push_str("\n");
1409        result.push_str(&"//\n".repeat(comment_lines));
1410        result.push_str(tail);
1411        result
1412    }
1413
1414    fn bracket_colors_markup(cx: &mut EditorTestContext) -> String {
1415        cx.update_editor(|editor, window, cx| {
1416            editor_bracket_colors_markup(&editor.snapshot(window, cx))
1417        })
1418    }
1419
1420    fn editor_bracket_colors_markup(snapshot: &EditorSnapshot) -> String {
1421        fn display_point_to_offset(text: &str, point: DisplayPoint) -> usize {
1422            let mut offset = 0;
1423            for (row_idx, line) in text.lines().enumerate() {
1424                if row_idx < point.row().0 as usize {
1425                    offset += line.len() + 1; // +1 for newline
1426                } else {
1427                    offset += point.column() as usize;
1428                    break;
1429                }
1430            }
1431            offset
1432        }
1433
1434        let actual_ranges = snapshot.all_text_highlight_ranges::<ColorizedBracketsHighlight>();
1435        let editor_text = snapshot.text();
1436
1437        let mut next_index = 1;
1438        let mut color_to_index = HashMap::default();
1439        let mut annotations = Vec::new();
1440        for (color, range) in &actual_ranges {
1441            let color_index = *color_to_index
1442                .entry(*color)
1443                .or_insert_with(|| post_inc(&mut next_index));
1444            let start = snapshot.point_to_display_point(range.start, Bias::Left);
1445            let end = snapshot.point_to_display_point(range.end, Bias::Right);
1446            let start_offset = display_point_to_offset(&editor_text, start);
1447            let end_offset = display_point_to_offset(&editor_text, end);
1448            let bracket_text = &editor_text[start_offset..end_offset];
1449            let bracket_char = bracket_text.chars().next().unwrap();
1450
1451            if matches!(bracket_char, '{' | '[' | '(' | '<') {
1452                annotations.push((start_offset, format!("«{color_index}")));
1453            } else {
1454                annotations.push((end_offset, format!("{color_index}»")));
1455            }
1456        }
1457
1458        annotations.sort_by(|(pos_a, text_a), (pos_b, text_b)| {
1459            pos_a.cmp(pos_b).reverse().then_with(|| {
1460                let a_is_opening = text_a.starts_with('«');
1461                let b_is_opening = text_b.starts_with('«');
1462                match (a_is_opening, b_is_opening) {
1463                    (true, false) => cmp::Ordering::Less,
1464                    (false, true) => cmp::Ordering::Greater,
1465                    _ => cmp::Ordering::Equal,
1466                }
1467            })
1468        });
1469        annotations.dedup();
1470
1471        let mut markup = editor_text;
1472        for (offset, text) in annotations {
1473            markup.insert_str(offset, &text);
1474        }
1475
1476        markup.push_str("\n");
1477        for (index, color) in color_to_index
1478            .iter()
1479            .map(|(color, index)| (*index, *color))
1480            .sorted_by_key(|(index, _)| *index)
1481        {
1482            markup.push_str(&format!("{index} {color}\n"));
1483        }
1484
1485        markup
1486    }
1487}