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