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;
   9use gpui::{Context, HighlightStyle};
  10use itertools::Itertools;
  11use language::language_settings;
  12use multi_buffer::{Anchor, ExcerptId};
  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.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        let all_excerpts = self.buffer().read(cx).excerpt_ids();
  28        let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
  29                                       text_anchors: [text::Anchor; 4]|
  30         -> Option<[Option<_>; 4]> {
  31            multi_buffer_snapshot
  32                .anchors_in_excerpt(current_excerpt, text_anchors)
  33                .or_else(|| {
  34                    all_excerpts
  35                        .iter()
  36                        .filter(|&&excerpt_id| excerpt_id != current_excerpt)
  37                        .find_map(|&excerpt_id| {
  38                            multi_buffer_snapshot.anchors_in_excerpt(excerpt_id, text_anchors)
  39                        })
  40                })?
  41                .collect_array()
  42        };
  43
  44        let bracket_matches_by_accent = self.visible_excerpts(false, cx).into_iter().fold(
  45            HashMap::default(),
  46            |mut acc, (excerpt_id, (buffer, _, buffer_range))| {
  47                let buffer_snapshot = buffer.read(cx).snapshot();
  48                if language_settings::language_settings(
  49                    buffer_snapshot.language().map(|language| language.name()),
  50                    buffer_snapshot.file(),
  51                    cx,
  52                )
  53                .colorize_brackets
  54                {
  55                    let fetched_chunks = self
  56                        .fetched_tree_sitter_chunks
  57                        .entry(excerpt_id)
  58                        .or_default();
  59
  60                    let brackets_by_accent = buffer_snapshot
  61                        .fetch_bracket_ranges(
  62                            buffer_range.start..buffer_range.end,
  63                            Some(fetched_chunks),
  64                        )
  65                        .into_iter()
  66                        .flat_map(|(chunk_range, pairs)| {
  67                            if fetched_chunks.insert(chunk_range) {
  68                                pairs
  69                            } else {
  70                                Vec::new()
  71                            }
  72                        })
  73                        .filter_map(|pair| {
  74                            let color_index = pair.color_index?;
  75
  76                            let buffer_open_range = buffer_snapshot
  77                                .anchor_before(pair.open_range.start)
  78                                ..buffer_snapshot.anchor_after(pair.open_range.end);
  79                            let buffer_close_range = buffer_snapshot
  80                                .anchor_before(pair.close_range.start)
  81                                ..buffer_snapshot.anchor_after(pair.close_range.end);
  82                            let [
  83                                buffer_open_range_start,
  84                                buffer_open_range_end,
  85                                buffer_close_range_start,
  86                                buffer_close_range_end,
  87                            ] = anchors_in_multi_buffer(
  88                                excerpt_id,
  89                                [
  90                                    buffer_open_range.start,
  91                                    buffer_open_range.end,
  92                                    buffer_close_range.start,
  93                                    buffer_close_range.end,
  94                                ],
  95                            )?;
  96                            let multi_buffer_open_range =
  97                                buffer_open_range_start.zip(buffer_open_range_end);
  98                            let multi_buffer_close_range =
  99                                buffer_close_range_start.zip(buffer_close_range_end);
 100
 101                            let mut ranges = Vec::with_capacity(2);
 102                            if let Some((open_start, open_end)) = multi_buffer_open_range {
 103                                ranges.push(open_start..open_end);
 104                            }
 105                            if let Some((close_start, close_end)) = multi_buffer_close_range {
 106                                ranges.push(close_start..close_end);
 107                            }
 108                            if ranges.is_empty() {
 109                                None
 110                            } else {
 111                                Some((color_index % accents_count, ranges))
 112                            }
 113                        });
 114
 115                    for (accent_number, new_ranges) in brackets_by_accent {
 116                        let ranges = acc
 117                            .entry(accent_number)
 118                            .or_insert_with(Vec::<Range<Anchor>>::new);
 119
 120                        for new_range in new_ranges {
 121                            let i = ranges
 122                                .binary_search_by(|probe| {
 123                                    probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
 124                                })
 125                                .unwrap_or_else(|i| i);
 126                            ranges.insert(i, new_range);
 127                        }
 128                    }
 129                }
 130
 131                acc
 132            },
 133        );
 134
 135        if invalidate {
 136            self.clear_highlights_with(|key| matches!(key, HighlightKey::ColorizeBracket(_)), cx);
 137        }
 138
 139        let editor_background = cx.theme().colors().editor_background;
 140        for (accent_number, bracket_highlights) in bracket_matches_by_accent {
 141            let bracket_color = cx.theme().accents().color_for_index(accent_number as u32);
 142            let adjusted_color = ensure_minimum_contrast(bracket_color, editor_background, 55.0);
 143            let style = HighlightStyle {
 144                color: Some(adjusted_color),
 145                ..HighlightStyle::default()
 146            };
 147
 148            self.highlight_text_key(
 149                HighlightKey::ColorizeBracket(accent_number),
 150                bracket_highlights,
 151                style,
 152                true,
 153                cx,
 154            );
 155        }
 156    }
 157}
 158
 159#[cfg(test)]
 160mod tests {
 161    use std::{cmp, sync::Arc, time::Duration};
 162
 163    use super::*;
 164    use crate::{
 165        DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
 166        display_map::{DisplayRow, ToDisplayPoint},
 167        editor_tests::init_test,
 168        test::{
 169            editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
 170        },
 171    };
 172    use collections::HashSet;
 173    use fs::FakeFs;
 174    use gpui::{AppContext as _, UpdateGlobal as _};
 175    use indoc::indoc;
 176    use itertools::Itertools;
 177    use language::{Capability, markdown_lang};
 178    use languages::rust_lang;
 179    use multi_buffer::{ExcerptRange, MultiBuffer};
 180    use pretty_assertions::assert_eq;
 181    use project::Project;
 182    use rope::Point;
 183    use serde_json::json;
 184    use settings::{AccentContent, SettingsStore};
 185    use text::{Bias, OffsetRangeExt, ToOffset};
 186    use theme::ThemeStyleContent;
 187
 188    use util::{path, post_inc};
 189
 190    #[gpui::test]
 191    async fn test_basic_bracket_colorization(cx: &mut gpui::TestAppContext) {
 192        init_test(cx, |language_settings| {
 193            language_settings.defaults.colorize_brackets = Some(true);
 194        });
 195        let mut cx = EditorLspTestContext::new(
 196            Arc::into_inner(rust_lang()).unwrap(),
 197            lsp::ServerCapabilities::default(),
 198            cx,
 199        )
 200        .await;
 201
 202        cx.set_state(indoc! {r#"ˇuse std::{collections::HashMap, future::Future};
 203
 204fn main() {
 205    let a = one((), { () }, ());
 206    println!("{a}");
 207    println!("{a}");
 208    for i in 0..a {
 209        println!("{i}");
 210    }
 211
 212    let b = {
 213        {
 214            {
 215                [([([([([([([([([([((), ())])])])])])])])])])]
 216            }
 217        }
 218    };
 219}
 220
 221#[rustfmt::skip]
 222fn one(a: (), (): (), c: ()) -> usize { 1 }
 223
 224fn two<T>(a: HashMap<String, Vec<Option<T>>>) -> usize
 225where
 226    T: Future<Output = HashMap<String, Vec<Option<Box<()>>>>>,
 227{
 228    2
 229}
 230"#});
 231        cx.executor().advance_clock(Duration::from_millis(100));
 232        cx.executor().run_until_parked();
 233
 234        assert_eq!(
 235            r#"use std::«1{collections::HashMap, future::Future}1»;
 236
 237fn main«1()1» «1{
 238    let a = one«2(«3()3», «3{ «4()4» }3», «3()3»)2»;
 239    println!«2("{a}")2»;
 240    println!«2("{a}")2»;
 241    for i in 0..a «2{
 242        println!«3("{i}")3»;
 243    }2»
 244
 245    let b = «2{
 246        «3{
 247            «4{
 248                «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»
 249            }4»
 250        }3»
 251    }2»;
 252}1»
 253
 254#«1[rustfmt::skip]1»
 255fn one«1(a: «2()2», «2()2»: «2()2», c: «2()2»)1» -> usize «1{ 1 }1»
 256
 257fn two«1<T>1»«1(a: HashMap«2<String, Vec«3<Option«4<T>4»>3»>2»)1» -> usize
 258where
 259    T: Future«1<Output = HashMap«2<String, Vec«3<Option«4<Box«5<«6()6»>5»>4»>3»>2»>1»,
 260«1{
 261    2
 262}1»
 263
 2641 hsla(207.80, 16.20%, 69.19%, 1.00)
 2652 hsla(29.00, 54.00%, 65.88%, 1.00)
 2663 hsla(286.00, 51.00%, 75.25%, 1.00)
 2674 hsla(187.00, 47.00%, 59.22%, 1.00)
 2685 hsla(355.00, 65.00%, 75.94%, 1.00)
 2696 hsla(95.00, 38.00%, 62.00%, 1.00)
 2707 hsla(39.00, 67.00%, 69.00%, 1.00)
 271"#,
 272            &bracket_colors_markup(&mut cx),
 273            "All brackets should be colored based on their depth"
 274        );
 275    }
 276
 277    #[gpui::test]
 278    async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
 279        init_test(cx, |language_settings| {
 280            language_settings.defaults.colorize_brackets = Some(true);
 281        });
 282        let editor = cx.add_window(|window, cx| {
 283            let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
 284            multi_buffer.update(cx, |multi_buffer, cx| {
 285                multi_buffer
 286                    .as_singleton()
 287                    .unwrap()
 288                    .update(cx, |buffer, cx| {
 289                        buffer.set_language(Some(rust_lang()), cx);
 290                    });
 291            });
 292            Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 293        });
 294
 295        cx.executor().advance_clock(Duration::from_millis(100));
 296        cx.executor().run_until_parked();
 297
 298        assert_eq!(
 299            "fn main«1()1» «1{}1»
 3001 hsla(207.80, 16.20%, 69.19%, 1.00)
 301",
 302            editor
 303                .update(cx, |editor, window, cx| {
 304                    editor_bracket_colors_markup(&editor.snapshot(window, cx))
 305                })
 306                .unwrap(),
 307            "File-less buffer should still have its brackets colorized"
 308        );
 309    }
 310
 311    #[gpui::test]
 312    async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
 313        init_test(cx, |language_settings| {
 314            language_settings.defaults.colorize_brackets = Some(true);
 315        });
 316        let mut cx = EditorLspTestContext::new(
 317            Arc::into_inner(markdown_lang()).unwrap(),
 318            lsp::ServerCapabilities::default(),
 319            cx,
 320        )
 321        .await;
 322
 323        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)"#});
 324        cx.executor().advance_clock(Duration::from_millis(100));
 325        cx.executor().run_until_parked();
 326
 327        assert_eq!(
 328            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»
 3291 hsla(207.80, 16.20%, 69.19%, 1.00)
 330"#,
 331            &bracket_colors_markup(&mut cx),
 332            "All markdown brackets should be colored based on their depth"
 333        );
 334
 335        cx.set_state(indoc! {r#"ˇ{{}}"#});
 336        cx.executor().advance_clock(Duration::from_millis(100));
 337        cx.executor().run_until_parked();
 338
 339        assert_eq!(
 340            r#"«1{«2{}2»}1»
 3411 hsla(207.80, 16.20%, 69.19%, 1.00)
 3422 hsla(29.00, 54.00%, 65.88%, 1.00)
 343"#,
 344            &bracket_colors_markup(&mut cx),
 345            "All markdown brackets should be colored based on their depth, again"
 346        );
 347    }
 348
 349    #[gpui::test]
 350    async fn test_markdown_brackets_in_multiple_hunks(cx: &mut gpui::TestAppContext) {
 351        init_test(cx, |language_settings| {
 352            language_settings.defaults.colorize_brackets = Some(true);
 353        });
 354        let mut cx = EditorLspTestContext::new(
 355            Arc::into_inner(markdown_lang()).unwrap(),
 356            lsp::ServerCapabilities::default(),
 357            cx,
 358        )
 359        .await;
 360
 361        let rows = 100;
 362        let footer = "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n";
 363
 364        let simple_brackets = (0..rows).map(|_| "ˇ[]\n").collect::<String>();
 365        let simple_brackets_highlights = (0..rows).map(|_| "«1[]1»\n").collect::<String>();
 366        cx.set_state(&simple_brackets);
 367        cx.update_editor(|editor, window, cx| {
 368            editor.move_to_end(&MoveToEnd, window, cx);
 369        });
 370        cx.executor().advance_clock(Duration::from_millis(100));
 371        cx.executor().run_until_parked();
 372        assert_eq!(
 373            format!("{simple_brackets_highlights}\n{footer}"),
 374            bracket_colors_markup(&mut cx),
 375            "Simple bracket pairs should be colored"
 376        );
 377
 378        let paired_brackets = (0..rows).map(|_| "ˇ[]()\n").collect::<String>();
 379        let paired_brackets_highlights = (0..rows).map(|_| "«1[]1»«1()1»\n").collect::<String>();
 380        cx.set_state(&paired_brackets);
 381        // Wait for reparse to complete after content change
 382        cx.executor().advance_clock(Duration::from_millis(100));
 383        cx.executor().run_until_parked();
 384        cx.update_editor(|editor, _, cx| {
 385            // Force invalidation of bracket cache after reparse
 386            editor.colorize_brackets(true, cx);
 387        });
 388        // Scroll to beginning to fetch first chunks
 389        cx.update_editor(|editor, window, cx| {
 390            editor.move_to_beginning(&MoveToBeginning, window, cx);
 391        });
 392        cx.executor().advance_clock(Duration::from_millis(100));
 393        cx.executor().run_until_parked();
 394        // Scroll to end to fetch remaining chunks
 395        cx.update_editor(|editor, window, cx| {
 396            editor.move_to_end(&MoveToEnd, window, cx);
 397        });
 398        cx.executor().advance_clock(Duration::from_millis(100));
 399        cx.executor().run_until_parked();
 400        assert_eq!(
 401            format!("{paired_brackets_highlights}\n{footer}"),
 402            bracket_colors_markup(&mut cx),
 403            "Paired bracket pairs should be colored"
 404        );
 405    }
 406
 407    #[gpui::test]
 408    async fn test_bracket_colorization_after_language_swap(cx: &mut gpui::TestAppContext) {
 409        init_test(cx, |language_settings| {
 410            language_settings.defaults.colorize_brackets = Some(true);
 411        });
 412
 413        let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
 414        language_registry.add(markdown_lang());
 415        language_registry.add(rust_lang());
 416
 417        let mut cx = EditorTestContext::new(cx).await;
 418        cx.update_buffer(|buffer, cx| {
 419            buffer.set_language_registry(language_registry.clone());
 420            buffer.set_language(Some(markdown_lang()), cx);
 421        });
 422
 423        cx.set_state(indoc! {r#"
 424            fn main() {
 425                let v: Vec<Stringˇ> = vec![];
 426            }
 427        "#});
 428        cx.executor().advance_clock(Duration::from_millis(100));
 429        cx.executor().run_until_parked();
 430
 431        assert_eq!(
 432            r#"fn main«1()1» «1{
 433    let v: Vec<String> = vec!«2[]2»;
 434}1»
 435
 4361 hsla(207.80, 16.20%, 69.19%, 1.00)
 4372 hsla(29.00, 54.00%, 65.88%, 1.00)
 438"#,
 439            &bracket_colors_markup(&mut cx),
 440            "Markdown does not colorize <> brackets"
 441        );
 442
 443        cx.update_buffer(|buffer, cx| {
 444            buffer.set_language(Some(rust_lang()), cx);
 445        });
 446        cx.executor().advance_clock(Duration::from_millis(100));
 447        cx.executor().run_until_parked();
 448
 449        assert_eq!(
 450            r#"fn main«1()1» «1{
 451    let v: Vec«2<String>2» = vec!«2[]2»;
 452}1»
 453
 4541 hsla(207.80, 16.20%, 69.19%, 1.00)
 4552 hsla(29.00, 54.00%, 65.88%, 1.00)
 456"#,
 457            &bracket_colors_markup(&mut cx),
 458            "After switching to Rust, <> brackets are now colorized"
 459        );
 460    }
 461
 462    #[gpui::test]
 463    async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
 464        init_test(cx, |language_settings| {
 465            language_settings.defaults.colorize_brackets = Some(true);
 466        });
 467        let mut cx = EditorLspTestContext::new(
 468            Arc::into_inner(rust_lang()).unwrap(),
 469            lsp::ServerCapabilities::default(),
 470            cx,
 471        )
 472        .await;
 473
 474        cx.set_state(indoc! {r#"
 475struct Foo<'a, T> {
 476    data: Vec<Option<&'a T>>,
 477}
 478
 479fn process_data() {
 480    let map:ˇ
 481}
 482"#});
 483
 484        cx.update_editor(|editor, window, cx| {
 485            editor.handle_input(" Result<", window, cx);
 486        });
 487        cx.executor().advance_clock(Duration::from_millis(100));
 488        cx.executor().run_until_parked();
 489        assert_eq!(
 490            indoc! {r#"
 491struct Foo«1<'a, T>1» «1{
 492    data: Vec«2<Option«3<&'a T>3»>2»,
 493}1»
 494
 495fn process_data«1()1» «1{
 496    let map: Result<
 497}1»
 498
 4991 hsla(207.80, 16.20%, 69.19%, 1.00)
 5002 hsla(29.00, 54.00%, 65.88%, 1.00)
 5013 hsla(286.00, 51.00%, 75.25%, 1.00)
 502"#},
 503            &bracket_colors_markup(&mut cx),
 504            "Brackets without pairs should be ignored and not colored"
 505        );
 506
 507        cx.update_editor(|editor, window, cx| {
 508            editor.handle_input("Option<Foo<'_, ()", window, cx);
 509        });
 510        cx.executor().advance_clock(Duration::from_millis(100));
 511        cx.executor().run_until_parked();
 512        assert_eq!(
 513            indoc! {r#"
 514struct Foo«1<'a, T>1» «1{
 515    data: Vec«2<Option«3<&'a T>3»>2»,
 516}1»
 517
 518fn process_data«1()1» «1{
 519    let map: Result<Option<Foo<'_, «2()2»
 520}1»
 521
 5221 hsla(207.80, 16.20%, 69.19%, 1.00)
 5232 hsla(29.00, 54.00%, 65.88%, 1.00)
 5243 hsla(286.00, 51.00%, 75.25%, 1.00)
 525"#},
 526            &bracket_colors_markup(&mut cx),
 527        );
 528
 529        cx.update_editor(|editor, window, cx| {
 530            editor.handle_input(">", window, cx);
 531        });
 532        cx.executor().advance_clock(Duration::from_millis(100));
 533        cx.executor().run_until_parked();
 534        assert_eq!(
 535            indoc! {r#"
 536struct Foo«1<'a, T>1» «1{
 537    data: Vec«2<Option«3<&'a T>3»>2»,
 538}1»
 539
 540fn process_data«1()1» «1{
 541    let map: Result<Option<Foo«2<'_, «3()3»>2»
 542}1»
 543
 5441 hsla(207.80, 16.20%, 69.19%, 1.00)
 5452 hsla(29.00, 54.00%, 65.88%, 1.00)
 5463 hsla(286.00, 51.00%, 75.25%, 1.00)
 547"#},
 548            &bracket_colors_markup(&mut cx),
 549            "When brackets start to get closed, inner brackets are re-colored based on their depth"
 550        );
 551
 552        cx.update_editor(|editor, window, cx| {
 553            editor.handle_input(">", window, cx);
 554        });
 555        cx.executor().advance_clock(Duration::from_millis(100));
 556        cx.executor().run_until_parked();
 557        assert_eq!(
 558            indoc! {r#"
 559struct Foo«1<'a, T>1» «1{
 560    data: Vec«2<Option«3<&'a T>3»>2»,
 561}1»
 562
 563fn process_data«1()1» «1{
 564    let map: Result<Option«2<Foo«3<'_, «4()4»>3»>2»
 565}1»
 566
 5671 hsla(207.80, 16.20%, 69.19%, 1.00)
 5682 hsla(29.00, 54.00%, 65.88%, 1.00)
 5693 hsla(286.00, 51.00%, 75.25%, 1.00)
 5704 hsla(187.00, 47.00%, 59.22%, 1.00)
 571"#},
 572            &bracket_colors_markup(&mut cx),
 573        );
 574
 575        cx.update_editor(|editor, window, cx| {
 576            editor.handle_input(", ()> = unimplemented!();", window, cx);
 577        });
 578        cx.executor().advance_clock(Duration::from_millis(100));
 579        cx.executor().run_until_parked();
 580        assert_eq!(
 581            indoc! {r#"
 582struct Foo«1<'a, T>1» «1{
 583    data: Vec«2<Option«3<&'a T>3»>2»,
 584}1»
 585
 586fn process_data«1()1» «1{
 587    let map: Result«2<Option«3<Foo«4<'_, «5()5»>4»>3», «3()3»>2» = unimplemented!«2()2»;
 588}1»
 589
 5901 hsla(207.80, 16.20%, 69.19%, 1.00)
 5912 hsla(29.00, 54.00%, 65.88%, 1.00)
 5923 hsla(286.00, 51.00%, 75.25%, 1.00)
 5934 hsla(187.00, 47.00%, 59.22%, 1.00)
 5945 hsla(355.00, 65.00%, 75.94%, 1.00)
 595"#},
 596            &bracket_colors_markup(&mut cx),
 597        );
 598    }
 599
 600    #[gpui::test]
 601    async fn test_bracket_colorization_chunks(cx: &mut gpui::TestAppContext) {
 602        let comment_lines = 100;
 603
 604        init_test(cx, |language_settings| {
 605            language_settings.defaults.colorize_brackets = Some(true);
 606        });
 607        let mut cx = EditorLspTestContext::new(
 608            Arc::into_inner(rust_lang()).unwrap(),
 609            lsp::ServerCapabilities::default(),
 610            cx,
 611        )
 612        .await;
 613
 614        cx.set_state(&separate_with_comment_lines(
 615            indoc! {r#"
 616mod foo {
 617    ˇfn process_data_1() {
 618        let map: Option<Vec<()>> = None;
 619    }
 620"#},
 621            indoc! {r#"
 622    fn process_data_2() {
 623        let map: Option<Vec<()>> = None;
 624    }
 625}
 626"#},
 627            comment_lines,
 628        ));
 629
 630        cx.executor().advance_clock(Duration::from_millis(100));
 631        cx.executor().run_until_parked();
 632        assert_eq!(
 633            &separate_with_comment_lines(
 634                indoc! {r#"
 635mod foo «1{
 636    fn process_data_1«2()2» «2{
 637        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 638    }2»
 639"#},
 640                indoc! {r#"
 641    fn process_data_2() {
 642        let map: Option<Vec<()>> = None;
 643    }
 644}1»
 645
 6461 hsla(207.80, 16.20%, 69.19%, 1.00)
 6472 hsla(29.00, 54.00%, 65.88%, 1.00)
 6483 hsla(286.00, 51.00%, 75.25%, 1.00)
 6494 hsla(187.00, 47.00%, 59.22%, 1.00)
 6505 hsla(355.00, 65.00%, 75.94%, 1.00)
 651"#},
 652                comment_lines,
 653            ),
 654            &bracket_colors_markup(&mut cx),
 655            "First, the only visible chunk is getting the bracket highlights"
 656        );
 657
 658        cx.update_editor(|editor, window, cx| {
 659            editor.move_to_end(&MoveToEnd, window, cx);
 660            editor.move_up(&MoveUp, window, cx);
 661        });
 662        cx.executor().advance_clock(Duration::from_millis(100));
 663        cx.executor().run_until_parked();
 664        assert_eq!(
 665            &separate_with_comment_lines(
 666                indoc! {r#"
 667mod foo «1{
 668    fn process_data_1«2()2» «2{
 669        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 670    }2»
 671"#},
 672                indoc! {r#"
 673    fn process_data_2«2()2» «2{
 674        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 675    }2»
 676}1»
 677
 6781 hsla(207.80, 16.20%, 69.19%, 1.00)
 6792 hsla(29.00, 54.00%, 65.88%, 1.00)
 6803 hsla(286.00, 51.00%, 75.25%, 1.00)
 6814 hsla(187.00, 47.00%, 59.22%, 1.00)
 6825 hsla(355.00, 65.00%, 75.94%, 1.00)
 683"#},
 684                comment_lines,
 685            ),
 686            &bracket_colors_markup(&mut cx),
 687            "After scrolling to the bottom, both chunks should have the highlights"
 688        );
 689
 690        cx.update_editor(|editor, window, cx| {
 691            editor.handle_input("{{}}}", window, cx);
 692        });
 693        cx.executor().advance_clock(Duration::from_millis(100));
 694        cx.executor().run_until_parked();
 695        assert_eq!(
 696            &separate_with_comment_lines(
 697                indoc! {r#"
 698mod foo «1{
 699    fn process_data_1() {
 700        let map: Option<Vec<()>> = None;
 701    }
 702"#},
 703                indoc! {r#"
 704    fn process_data_2«2()2» «2{
 705        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 706    }
 707    «3{«4{}4»}3»}2»}1»
 708
 7091 hsla(207.80, 16.20%, 69.19%, 1.00)
 7102 hsla(29.00, 54.00%, 65.88%, 1.00)
 7113 hsla(286.00, 51.00%, 75.25%, 1.00)
 7124 hsla(187.00, 47.00%, 59.22%, 1.00)
 7135 hsla(355.00, 65.00%, 75.94%, 1.00)
 714"#},
 715                comment_lines,
 716            ),
 717            &bracket_colors_markup(&mut cx),
 718            "First chunk's brackets are invalidated after an edit, and only 2nd (visible) chunk is re-colorized"
 719        );
 720
 721        cx.update_editor(|editor, window, cx| {
 722            editor.move_to_beginning(&MoveToBeginning, window, cx);
 723        });
 724        cx.executor().advance_clock(Duration::from_millis(100));
 725        cx.executor().run_until_parked();
 726        assert_eq!(
 727            &separate_with_comment_lines(
 728                indoc! {r#"
 729mod foo «1{
 730    fn process_data_1«2()2» «2{
 731        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 732    }2»
 733"#},
 734                indoc! {r#"
 735    fn process_data_2«2()2» «2{
 736        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 737    }
 738    «3{«4{}4»}3»}2»}1»
 739
 7401 hsla(207.80, 16.20%, 69.19%, 1.00)
 7412 hsla(29.00, 54.00%, 65.88%, 1.00)
 7423 hsla(286.00, 51.00%, 75.25%, 1.00)
 7434 hsla(187.00, 47.00%, 59.22%, 1.00)
 7445 hsla(355.00, 65.00%, 75.94%, 1.00)
 745"#},
 746                comment_lines,
 747            ),
 748            &bracket_colors_markup(&mut cx),
 749            "Scrolling back to top should re-colorize all chunks' brackets"
 750        );
 751
 752        cx.update(|_, cx| {
 753            SettingsStore::update_global(cx, |store, cx| {
 754                store.update_user_settings(cx, |settings| {
 755                    settings.project.all_languages.defaults.colorize_brackets = Some(false);
 756                });
 757            });
 758        });
 759        assert_eq!(
 760            &separate_with_comment_lines(
 761                indoc! {r#"
 762mod foo {
 763    fn process_data_1() {
 764        let map: Option<Vec<()>> = None;
 765    }
 766"#},
 767                r#"    fn process_data_2() {
 768        let map: Option<Vec<()>> = None;
 769    }
 770    {{}}}}
 771
 772"#,
 773                comment_lines,
 774            ),
 775            &bracket_colors_markup(&mut cx),
 776            "Turning bracket colorization off should remove all bracket colors"
 777        );
 778
 779        cx.update(|_, cx| {
 780            SettingsStore::update_global(cx, |store, cx| {
 781                store.update_user_settings(cx, |settings| {
 782                    settings.project.all_languages.defaults.colorize_brackets = Some(true);
 783                });
 784            });
 785        });
 786        assert_eq!(
 787            &separate_with_comment_lines(
 788                indoc! {r#"
 789mod foo «1{
 790    fn process_data_1«2()2» «2{
 791        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 792    }2»
 793"#},
 794                r#"    fn process_data_2() {
 795        let map: Option<Vec<()>> = None;
 796    }
 797    {{}}}}1»
 798
 7991 hsla(207.80, 16.20%, 69.19%, 1.00)
 8002 hsla(29.00, 54.00%, 65.88%, 1.00)
 8013 hsla(286.00, 51.00%, 75.25%, 1.00)
 8024 hsla(187.00, 47.00%, 59.22%, 1.00)
 8035 hsla(355.00, 65.00%, 75.94%, 1.00)
 804"#,
 805                comment_lines,
 806            ),
 807            &bracket_colors_markup(&mut cx),
 808            "Turning bracket colorization back on refreshes the visible excerpts' bracket colors"
 809        );
 810    }
 811
 812    #[gpui::test]
 813    async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
 814        init_test(cx, |language_settings| {
 815            language_settings.defaults.colorize_brackets = Some(true);
 816        });
 817        let mut cx = EditorLspTestContext::new(
 818            Arc::into_inner(rust_lang()).unwrap(),
 819            lsp::ServerCapabilities::default(),
 820            cx,
 821        )
 822        .await;
 823
 824        // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
 825        cx.set_state(indoc! {r# 826            pub(crate) fn inlay_hints(
 827                db: &RootDatabase,
 828                file_id: FileId,
 829                range_limit: Option<TextRange>,
 830                config: &InlayHintsConfig,
 831            ) -> Vec<InlayHint> {
 832                let _p = tracing::info_span!("inlay_hints").entered();
 833                let sema = Semantics::new(db);
 834                let file_id = sema
 835                    .attach_first_edition(file_id)
 836                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 837                let file = sema.parse(file_id);
 838                let file = file.syntax();
 839
 840                let mut acc = Vec::new();
 841
 842                let Some(scope) = sema.scope(file) else {
 843                    return acc;
 844                };
 845                let famous_defs = FamousDefs(&sema, scope.krate());
 846                let display_target = famous_defs.1.to_display_target(sema.db);
 847
 848                let ctx = &mut InlayHintCtx::default();
 849                let mut hints = |event| {
 850                    if let Some(node) = handle_event(ctx, event) {
 851                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 852                    }
 853                };
 854                let mut preorder = file.preorder();
 855                salsa::attach(sema.db, || {
 856                    while let Some(event) = preorder.next() {
 857                        if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
 858                        {
 859                            preorder.skip_subtree();
 860                            continue;
 861                        }
 862                        hints(event);
 863                    }
 864                });
 865                if let Some(range_limit) = range_limit {
 866                    acc.retain(|hint| range_limit.contains_range(hint.range));
 867                }
 868                acc
 869            }
 870
 871            #[derive(Default)]
 872            struct InlayHintCtx {
 873                lifetime_stacks: Vec<Vec<SmolStr>>,
 874                extern_block_parent: Option<ast::ExternBlock>,
 875            }
 876
 877            pub(crate) fn inlay_hints_resolve(
 878                db: &RootDatabase,
 879                file_id: FileId,
 880                resolve_range: TextRange,
 881                hash: u64,
 882                config: &InlayHintsConfig,
 883                hasher: impl Fn(&InlayHint) -> u64,
 884            ) -> Option<InlayHint> {
 885                let _p = tracing::info_span!("inlay_hints_resolve").entered();
 886                let sema = Semantics::new(db);
 887                let file_id = sema
 888                    .attach_first_edition(file_id)
 889                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 890                let file = sema.parse(file_id);
 891                let file = file.syntax();
 892
 893                let scope = sema.scope(file)?;
 894                let famous_defs = FamousDefs(&sema, scope.krate());
 895                let mut acc = Vec::new();
 896
 897                let display_target = famous_defs.1.to_display_target(sema.db);
 898
 899                let ctx = &mut InlayHintCtx::default();
 900                let mut hints = |event| {
 901                    if let Some(node) = handle_event(ctx, event) {
 902                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 903                    }
 904                };
 905
 906                let mut preorder = file.preorder();
 907                while let Some(event) = preorder.next() {
 908                    // This can miss some hints that require the parent of the range to calculate
 909                    if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
 910                    {
 911                        preorder.skip_subtree();
 912                        continue;
 913                    }
 914                    hints(event);
 915                }
 916                acc.into_iter().find(|hint| hasher(hint) == hash)
 917            }
 918
 919            fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
 920                match node {
 921                    WalkEvent::Enter(node) => {
 922                        if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
 923                            let params = node
 924                                .generic_param_list()
 925                                .map(|it| {
 926                                    it.lifetime_params()
 927                                        .filter_map(|it| {
 928                                            it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
 929                                        })
 930                                        .collect()
 931                                })
 932                                .unwrap_or_default();
 933                            ctx.lifetime_stacks.push(params);
 934                        }
 935                        if let Some(node) = ast::ExternBlock::cast(node.clone()) {
 936                            ctx.extern_block_parent = Some(node);
 937                        }
 938                        Some(node)
 939                    }
 940                    WalkEvent::Leave(n) => {
 941                        if ast::AnyHasGenericParams::can_cast(n.kind()) {
 942                            ctx.lifetime_stacks.pop();
 943                        }
 944                        if ast::ExternBlock::can_cast(n.kind()) {
 945                            ctx.extern_block_parent = None;
 946                        }
 947                        None
 948                    }
 949                }
 950            }
 951
 952            // At some point when our hir infra is fleshed out enough we should flip this and traverse the
 953            // HIR instead of the syntax tree.
 954            fn hints(
 955                hints: &mut Vec<InlayHint>,
 956                ctx: &mut InlayHintCtx,
 957                famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
 958                config: &InlayHintsConfig,
 959                file_id: EditionedFileId,
 960                display_target: DisplayTarget,
 961                node: SyntaxNode,
 962            ) {
 963                closing_brace::hints(
 964                    hints,
 965                    sema,
 966                    config,
 967                    display_target,
 968                    InRealFile { file_id, value: node.clone() },
 969                );
 970                if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
 971                    generic_param::hints(hints, famous_defs, config, any_has_generic_args);
 972                }
 973
 974                match_ast! {
 975                    match node {
 976                        ast::Expr(expr) => {
 977                            chaining::hints(hints, famous_defs, config, display_target, &expr);
 978                            adjustment::hints(hints, famous_defs, config, display_target, &expr);
 979                            match expr {
 980                                ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
 981                                ast::Expr::MethodCallExpr(it) => {
 982                                    param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
 983                                }
 984                                ast::Expr::ClosureExpr(it) => {
 985                                    closure_captures::hints(hints, famous_defs, config, it.clone());
 986                                    closure_ret::hints(hints, famous_defs, config, display_target, it)
 987                                },
 988                                ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
 989                                _ => Some(()),
 990                            }
 991                        },
 992                        ast::Pat(it) => {
 993                            binding_mode::hints(hints, famous_defs, config, &it);
 994                            match it {
 995                                ast::Pat::IdentPat(it) => {
 996                                    bind_pat::hints(hints, famous_defs, config, display_target, &it);
 997                                }
 998                                ast::Pat::RangePat(it) => {
 999                                    range_exclusive::hints(hints, famous_defs, config, it);
1000                                }
1001                                _ => {}
1002                            }
1003                            Some(())
1004                        },
1005                        ast::Item(it) => match it {
1006                            ast::Item::Fn(it) => {
1007                                implicit_drop::hints(hints, famous_defs, config, display_target, &it);
1008                                if let Some(extern_block) = &ctx.extern_block_parent {
1009                                    extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
1010                                }
1011                                lifetime::fn_hints(hints, ctx, famous_defs, config,  it)
1012                            },
1013                            ast::Item::Static(it) => {
1014                                if let Some(extern_block) = &ctx.extern_block_parent {
1015                                    extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
1016                                }
1017                                implicit_static::hints(hints, famous_defs, config,  Either::Left(it))
1018                            },
1019                            ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
1020                            ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
1021                            ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
1022                            _ => None,
1023                        },
1024                        // trait object type elisions
1025                        ast::Type(ty) => match ty {
1026                            ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config,  ptr),
1027                            ast::Type::PathType(path) => {
1028                                lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
1029                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
1030                                Some(())
1031                            },
1032                            ast::Type::DynTraitType(dyn_) => {
1033                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
1034                                Some(())
1035                            },
1036                            _ => Some(()),
1037                        },
1038                        ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config,  it),
1039                        _ => Some(()),
1040                    }
1041                };
1042            }
1043        "#});
1044        cx.executor().advance_clock(Duration::from_millis(100));
1045        cx.executor().run_until_parked();
1046
1047        let actual_ranges = cx.update_editor(|editor, window, cx| {
1048            editor
1049                .snapshot(window, cx)
1050                .all_text_highlight_ranges(|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1051        });
1052
1053        let mut highlighted_brackets = HashMap::default();
1054        for (color, range) in actual_ranges.iter().cloned() {
1055            highlighted_brackets.insert(range, color);
1056        }
1057
1058        let last_bracket = actual_ranges
1059            .iter()
1060            .max_by_key(|(_, p)| p.end.row)
1061            .unwrap()
1062            .clone();
1063
1064        cx.update_editor(|editor, window, cx| {
1065            let was_scrolled = editor.set_scroll_position(
1066                gpui::Point::new(0.0, last_bracket.1.end.row as f64 * 2.0),
1067                window,
1068                cx,
1069            );
1070            assert!(was_scrolled.0);
1071        });
1072        cx.executor().advance_clock(Duration::from_millis(100));
1073        cx.executor().run_until_parked();
1074
1075        let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
1076            editor
1077                .snapshot(window, cx)
1078                .all_text_highlight_ranges(|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1079        });
1080        let new_last_bracket = ranges_after_scrolling
1081            .iter()
1082            .max_by_key(|(_, p)| p.end.row)
1083            .unwrap()
1084            .clone();
1085
1086        assert_ne!(
1087            last_bracket, new_last_bracket,
1088            "After scrolling down, we should have highlighted more brackets"
1089        );
1090
1091        cx.update_editor(|editor, window, cx| {
1092            let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
1093            assert!(was_scrolled.0);
1094        });
1095
1096        for _ in 0..200 {
1097            cx.update_editor(|editor, window, cx| {
1098                editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
1099            });
1100            cx.executor().advance_clock(Duration::from_millis(100));
1101            cx.executor().run_until_parked();
1102
1103            let colored_brackets = cx.update_editor(|editor, window, cx| {
1104                editor
1105                    .snapshot(window, cx)
1106                    .all_text_highlight_ranges(|key| {
1107                        matches!(key, HighlightKey::ColorizeBracket(_))
1108                    })
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
1435            .all_text_highlight_ranges(|key| matches!(key, HighlightKey::ColorizeBracket(_)));
1436        let editor_text = snapshot.text();
1437
1438        let mut next_index = 1;
1439        let mut color_to_index = HashMap::default();
1440        let mut annotations = Vec::new();
1441        for (color, range) in &actual_ranges {
1442            let color_index = *color_to_index
1443                .entry(*color)
1444                .or_insert_with(|| post_inc(&mut next_index));
1445            let start = snapshot.point_to_display_point(range.start, Bias::Left);
1446            let end = snapshot.point_to_display_point(range.end, Bias::Right);
1447            let start_offset = display_point_to_offset(&editor_text, start);
1448            let end_offset = display_point_to_offset(&editor_text, end);
1449            let bracket_text = &editor_text[start_offset..end_offset];
1450            let bracket_char = bracket_text.chars().next().unwrap();
1451
1452            if matches!(bracket_char, '{' | '[' | '(' | '<') {
1453                annotations.push((start_offset, format!("«{color_index}")));
1454            } else {
1455                annotations.push((end_offset, format!("{color_index}»")));
1456            }
1457        }
1458
1459        annotations.sort_by(|(pos_a, text_a), (pos_b, text_b)| {
1460            pos_a.cmp(pos_b).reverse().then_with(|| {
1461                let a_is_opening = text_a.starts_with('«');
1462                let b_is_opening = text_b.starts_with('«');
1463                match (a_is_opening, b_is_opening) {
1464                    (true, false) => cmp::Ordering::Less,
1465                    (false, true) => cmp::Ordering::Greater,
1466                    _ => cmp::Ordering::Equal,
1467                }
1468            })
1469        });
1470        annotations.dedup();
1471
1472        let mut markup = editor_text;
1473        for (offset, text) in annotations {
1474            markup.insert_str(offset, &text);
1475        }
1476
1477        markup.push_str("\n");
1478        for (index, color) in color_to_index
1479            .iter()
1480            .map(|(color, index)| (*index, *color))
1481            .sorted_by_key(|(index, _)| *index)
1482        {
1483            markup.push_str(&format!("{index} {color}\n"));
1484        }
1485
1486        markup
1487    }
1488}