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