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