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