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