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