bracket_colorization.rs

   1//! Bracket highlights, also known as "rainbow brackets".
   2//! Uses tree-sitter queries from brackets.scm to capture bracket pairs,
   3//! and theme accents to colorize those.
   4
   5use std::ops::Range;
   6
   7use crate::{Editor, HighlightKey};
   8use collections::{HashMap, HashSet};
   9use gpui::{AppContext as _, Context, HighlightStyle};
  10use language::{BufferRow, BufferSnapshot, language_settings::LanguageSettings};
  11use multi_buffer::{Anchor, BufferOffset, ExcerptRange, MultiBufferSnapshot};
  12use text::OffsetRangeExt as _;
  13use ui::{ActiveTheme, utils::ensure_minimum_contrast};
  14
  15impl Editor {
  16    pub(crate) fn colorize_brackets(&mut self, invalidate: bool, cx: &mut Context<Editor>) {
  17        if !self.mode.is_full() {
  18            return;
  19        }
  20
  21        if invalidate {
  22            self.bracket_fetched_tree_sitter_chunks.clear();
  23        }
  24
  25        let accents_count = cx.theme().accents().0.len();
  26        let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
  27
  28        let visible_excerpts = self.visible_buffer_ranges(cx);
  29        let excerpt_data: Vec<(
  30            BufferSnapshot,
  31            Range<BufferOffset>,
  32            ExcerptRange<text::Anchor>,
  33        )> = visible_excerpts
  34            .into_iter()
  35            .filter(|(buffer_snapshot, _, _)| {
  36                let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id())
  37                else {
  38                    return false;
  39                };
  40                LanguageSettings::for_buffer(buffer.read(cx), cx).colorize_brackets
  41            })
  42            .collect();
  43
  44        let mut fetched_tree_sitter_chunks = excerpt_data
  45            .iter()
  46            .filter_map(|(_, _, excerpt_range)| {
  47                let key = excerpt_range.context.clone();
  48                Some((
  49                    key.clone(),
  50                    self.bracket_fetched_tree_sitter_chunks.get(&key).cloned()?,
  51                ))
  52            })
  53            .collect::<HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>>();
  54
  55        let bracket_matches_by_accent = cx.background_spawn(async move {
  56            let bracket_matches_by_accent: HashMap<usize, Vec<Range<Anchor>>> =
  57                excerpt_data.into_iter().fold(
  58                    HashMap::default(),
  59                    |mut acc, (buffer_snapshot, buffer_range, excerpt_range)| {
  60                        let fetched_chunks = fetched_tree_sitter_chunks
  61                            .entry(excerpt_range.context.clone())
  62                            .or_default();
  63
  64                        let brackets_by_accent = compute_bracket_ranges(
  65                            &multi_buffer_snapshot,
  66                            &buffer_snapshot,
  67                            buffer_range,
  68                            excerpt_range,
  69                            fetched_chunks,
  70                            accents_count,
  71                        );
  72
  73                        for (accent_number, new_ranges) in brackets_by_accent {
  74                            let ranges = acc
  75                                .entry(accent_number)
  76                                .or_insert_with(Vec::<Range<Anchor>>::new);
  77
  78                            for new_range in new_ranges {
  79                                let i = ranges
  80                                    .binary_search_by(|probe| {
  81                                        probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
  82                                    })
  83                                    .unwrap_or_else(|i| i);
  84                                ranges.insert(i, new_range);
  85                            }
  86                        }
  87
  88                        acc
  89                    },
  90                );
  91
  92            (bracket_matches_by_accent, fetched_tree_sitter_chunks)
  93        });
  94
  95        let editor_background = cx.theme().colors().editor_background;
  96        let accents = cx.theme().accents().clone();
  97
  98        self.colorize_brackets_task = cx.spawn(async move |editor, cx| {
  99            if invalidate {
 100                editor
 101                    .update(cx, |editor, cx| {
 102                        editor.clear_highlights_with(
 103                            &mut |key| matches!(key, HighlightKey::ColorizeBracket(_)),
 104                            cx,
 105                        );
 106                    })
 107                    .ok();
 108            }
 109
 110            let (bracket_matches_by_accent, updated_chunks) = bracket_matches_by_accent.await;
 111
 112            editor
 113                .update(cx, |editor, cx| {
 114                    editor
 115                        .bracket_fetched_tree_sitter_chunks
 116                        .extend(updated_chunks);
 117                    for (accent_number, bracket_highlights) in bracket_matches_by_accent {
 118                        let bracket_color = accents.color_for_index(accent_number as u32);
 119                        let adjusted_color =
 120                            ensure_minimum_contrast(bracket_color, editor_background, 55.0);
 121                        let style = HighlightStyle {
 122                            color: Some(adjusted_color),
 123                            ..HighlightStyle::default()
 124                        };
 125
 126                        editor.highlight_text_key(
 127                            HighlightKey::ColorizeBracket(accent_number),
 128                            bracket_highlights,
 129                            style,
 130                            true,
 131                            cx,
 132                        );
 133                    }
 134                })
 135                .ok();
 136        });
 137    }
 138}
 139
 140fn compute_bracket_ranges(
 141    multi_buffer_snapshot: &MultiBufferSnapshot,
 142    buffer_snapshot: &BufferSnapshot,
 143    buffer_range: Range<BufferOffset>,
 144    excerpt_range: ExcerptRange<text::Anchor>,
 145    fetched_chunks: &mut HashSet<Range<BufferRow>>,
 146    accents_count: usize,
 147) -> Vec<(usize, Vec<Range<Anchor>>)> {
 148    let context = excerpt_range.context.to_offset(buffer_snapshot);
 149
 150    buffer_snapshot
 151        .fetch_bracket_ranges(
 152            buffer_range.start.0..buffer_range.end.0,
 153            Some(fetched_chunks),
 154        )
 155        .into_iter()
 156        .flat_map(|(chunk_range, pairs)| {
 157            if fetched_chunks.insert(chunk_range) {
 158                pairs
 159            } else {
 160                Vec::new()
 161            }
 162        })
 163        .filter_map(|pair| {
 164            let color_index = pair.color_index?;
 165
 166            let mut ranges = Vec::new();
 167
 168            if context.start <= pair.open_range.start && pair.open_range.end <= context.end {
 169                let anchors = buffer_snapshot.anchor_range_inside(pair.open_range);
 170                ranges.push(
 171                    multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
 172                        ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
 173                );
 174            };
 175
 176            if context.start <= pair.close_range.start && pair.close_range.end <= context.end {
 177                let anchors = buffer_snapshot.anchor_range_inside(pair.close_range);
 178                ranges.push(
 179                    multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
 180                        ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
 181                );
 182            };
 183
 184            Some((color_index % accents_count, ranges))
 185        })
 186        .collect()
 187}
 188
 189#[cfg(test)]
 190mod tests {
 191    use std::{cmp, sync::Arc, time::Duration};
 192
 193    use super::*;
 194    use crate::{
 195        DisplayPoint, EditorMode, EditorSnapshot, MoveToBeginning, MoveToEnd, MoveUp,
 196        display_map::{DisplayRow, ToDisplayPoint},
 197        editor_tests::init_test,
 198        test::{
 199            editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
 200        },
 201    };
 202    use collections::HashSet;
 203    use fs::FakeFs;
 204    use gpui::UpdateGlobal as _;
 205    use indoc::indoc;
 206    use itertools::Itertools;
 207    use language::{Capability, markdown_lang};
 208    use languages::rust_lang;
 209    use multi_buffer::{MultiBuffer, PathKey};
 210    use pretty_assertions::assert_eq;
 211    use project::Project;
 212    use rope::Point;
 213    use serde_json::json;
 214    use settings::{AccentContent, SettingsStore};
 215    use text::{Bias, OffsetRangeExt, ToOffset};
 216    use theme_settings::ThemeStyleContent;
 217
 218    use util::{path, post_inc};
 219
 220    #[gpui::test]
 221    async fn test_basic_bracket_colorization(cx: &mut gpui::TestAppContext) {
 222        init_test(cx, |language_settings| {
 223            language_settings.defaults.colorize_brackets = Some(true);
 224        });
 225        let mut cx = EditorLspTestContext::new(
 226            Arc::into_inner(rust_lang()).unwrap(),
 227            lsp::ServerCapabilities::default(),
 228            cx,
 229        )
 230        .await;
 231
 232        cx.set_state(indoc! {r#"ˇuse std::{collections::HashMap, future::Future};
 233
 234fn main() {
 235    let a = one((), { () }, ());
 236    println!("{a}");
 237    println!("{a}");
 238    for i in 0..a {
 239        println!("{i}");
 240    }
 241
 242    let b = {
 243        {
 244            {
 245                [([([([([([([([([([((), ())])])])])])])])])])]
 246            }
 247        }
 248    };
 249}
 250
 251#[rustfmt::skip]
 252fn one(a: (), (): (), c: ()) -> usize { 1 }
 253
 254fn two<T>(a: HashMap<String, Vec<Option<T>>>) -> usize
 255where
 256    T: Future<Output = HashMap<String, Vec<Option<Box<()>>>>>,
 257{
 258    2
 259}
 260"#});
 261        cx.executor().advance_clock(Duration::from_millis(100));
 262        cx.executor().run_until_parked();
 263
 264        assert_eq!(
 265            r#"use std::«1{collections::HashMap, future::Future}1»;
 266
 267fn main«1()1» «1{
 268    let a = one«2(«3()3», «3{ «4()4» }3», «3()3»)2»;
 269    println!«2("{a}")2»;
 270    println!«2("{a}")2»;
 271    for i in 0..a «2{
 272        println!«3("{i}")3»;
 273    }2»
 274
 275    let b = «2{
 276        «3{
 277            «4{
 278                «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»
 279            }4»
 280        }3»
 281    }2»;
 282}1»
 283
 284#«1[rustfmt::skip]1»
 285fn one«1(a: «2()2», «2()2»: «2()2», c: «2()2»)1» -> usize «1{ 1 }1»
 286
 287fn two«1<T>1»«1(a: HashMap«2<String, Vec«3<Option«4<T>4»>3»>2»)1» -> usize
 288where
 289    T: Future«1<Output = HashMap«2<String, Vec«3<Option«4<Box«5<«6()6»>5»>4»>3»>2»>1»,
 290«1{
 291    2
 292}1»
 293
 2941 hsla(207.80, 16.20%, 69.19%, 1.00)
 2952 hsla(29.00, 54.00%, 65.88%, 1.00)
 2963 hsla(286.00, 51.00%, 75.25%, 1.00)
 2974 hsla(187.00, 47.00%, 59.22%, 1.00)
 2985 hsla(355.00, 65.00%, 75.94%, 1.00)
 2996 hsla(95.00, 38.00%, 62.00%, 1.00)
 3007 hsla(39.00, 67.00%, 69.00%, 1.00)
 301"#,
 302            &bracket_colors_markup(&mut cx),
 303            "All brackets should be colored based on their depth"
 304        );
 305    }
 306
 307    #[gpui::test]
 308    async fn test_file_less_file_colorization(cx: &mut gpui::TestAppContext) {
 309        init_test(cx, |language_settings| {
 310            language_settings.defaults.colorize_brackets = Some(true);
 311        });
 312        let editor = cx.add_window(|window, cx| {
 313            let multi_buffer = MultiBuffer::build_simple("fn main() {}", cx);
 314            multi_buffer.update(cx, |multi_buffer, cx| {
 315                multi_buffer
 316                    .as_singleton()
 317                    .unwrap()
 318                    .update(cx, |buffer, cx| {
 319                        buffer.set_language(Some(rust_lang()), cx);
 320                    });
 321            });
 322            Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 323        });
 324
 325        cx.executor().advance_clock(Duration::from_millis(100));
 326        cx.executor().run_until_parked();
 327
 328        assert_eq!(
 329            "fn main«1()1» «1{}1»
 3301 hsla(207.80, 16.20%, 69.19%, 1.00)
 331",
 332            editor
 333                .update(cx, |editor, window, cx| {
 334                    editor_bracket_colors_markup(&editor.snapshot(window, cx))
 335                })
 336                .unwrap(),
 337            "File-less buffer should still have its brackets colorized"
 338        );
 339    }
 340
 341    #[gpui::test]
 342    async fn test_markdown_bracket_colorization(cx: &mut gpui::TestAppContext) {
 343        init_test(cx, |language_settings| {
 344            language_settings.defaults.colorize_brackets = Some(true);
 345        });
 346        let mut cx = EditorLspTestContext::new(
 347            Arc::into_inner(markdown_lang()).unwrap(),
 348            lsp::ServerCapabilities::default(),
 349            cx,
 350        )
 351        .await;
 352
 353        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)"#});
 354        cx.executor().advance_clock(Duration::from_millis(100));
 355        cx.executor().run_until_parked();
 356
 357        assert_eq!(
 358            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»
 3591 hsla(207.80, 16.20%, 69.19%, 1.00)
 360"#,
 361            &bracket_colors_markup(&mut cx),
 362            "All markdown brackets should be colored based on their depth"
 363        );
 364
 365        cx.set_state(indoc! {r#"ˇ{{}}"#});
 366        cx.executor().advance_clock(Duration::from_millis(100));
 367        cx.executor().run_until_parked();
 368
 369        assert_eq!(
 370            r#"«1{«2{}2»}1»
 3711 hsla(207.80, 16.20%, 69.19%, 1.00)
 3722 hsla(29.00, 54.00%, 65.88%, 1.00)
 373"#,
 374            &bracket_colors_markup(&mut cx),
 375            "All markdown brackets should be colored based on their depth, again"
 376        );
 377
 378        cx.set_state(indoc! {r#"ˇ('')('')
 379
 380((''))('')
 381
 382('')((''))"#});
 383        cx.executor().advance_clock(Duration::from_millis(100));
 384        cx.executor().run_until_parked();
 385
 386        assert_eq!(
 387            "«1('')1»«1('')1»\n\n«1(«2('')2»)1»«1('')1»\n\n«1('')1»«1(«2('')2»)1»\n1 hsla(207.80, 16.20%, 69.19%, 1.00)\n2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
 388            &bracket_colors_markup(&mut cx),
 389            "Markdown quote pairs should not interfere with parenthesis pairing"
 390        );
 391    }
 392
 393    #[gpui::test]
 394    async fn test_markdown_brackets_in_multiple_hunks(cx: &mut gpui::TestAppContext) {
 395        init_test(cx, |language_settings| {
 396            language_settings.defaults.colorize_brackets = Some(true);
 397        });
 398        let mut cx = EditorLspTestContext::new(
 399            Arc::into_inner(markdown_lang()).unwrap(),
 400            lsp::ServerCapabilities::default(),
 401            cx,
 402        )
 403        .await;
 404
 405        let rows = 100;
 406        let footer = "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n";
 407
 408        let simple_brackets = (0..rows).map(|_| "ˇ[]\n").collect::<String>();
 409        let simple_brackets_highlights = (0..rows).map(|_| "«1[]1»\n").collect::<String>();
 410        cx.set_state(&simple_brackets);
 411        cx.update_editor(|editor, window, cx| {
 412            editor.move_to_end(&MoveToEnd, window, cx);
 413        });
 414        cx.executor().advance_clock(Duration::from_millis(100));
 415        cx.executor().run_until_parked();
 416        assert_eq!(
 417            format!("{simple_brackets_highlights}\n{footer}"),
 418            bracket_colors_markup(&mut cx),
 419            "Simple bracket pairs should be colored"
 420        );
 421
 422        let paired_brackets = (0..rows).map(|_| "ˇ[]()\n").collect::<String>();
 423        let paired_brackets_highlights = (0..rows).map(|_| "«1[]1»«1()1»\n").collect::<String>();
 424        cx.set_state(&paired_brackets);
 425        // Wait for reparse to complete after content change
 426        cx.executor().advance_clock(Duration::from_millis(100));
 427        cx.executor().run_until_parked();
 428        cx.update_editor(|editor, _, cx| {
 429            // Force invalidation of bracket cache after reparse
 430            editor.colorize_brackets(true, cx);
 431        });
 432        // Scroll to beginning to fetch first chunks
 433        cx.update_editor(|editor, window, cx| {
 434            editor.move_to_beginning(&MoveToBeginning, window, cx);
 435        });
 436        cx.executor().advance_clock(Duration::from_millis(100));
 437        cx.executor().run_until_parked();
 438        // Scroll to end to fetch remaining chunks
 439        cx.update_editor(|editor, window, cx| {
 440            editor.move_to_end(&MoveToEnd, window, cx);
 441        });
 442        cx.executor().advance_clock(Duration::from_millis(100));
 443        cx.executor().run_until_parked();
 444        assert_eq!(
 445            format!("{paired_brackets_highlights}\n{footer}"),
 446            bracket_colors_markup(&mut cx),
 447            "Paired bracket pairs should be colored"
 448        );
 449    }
 450
 451    #[gpui::test]
 452    async fn test_bracket_colorization_after_language_swap(cx: &mut gpui::TestAppContext) {
 453        init_test(cx, |language_settings| {
 454            language_settings.defaults.colorize_brackets = Some(true);
 455        });
 456
 457        let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
 458        language_registry.add(markdown_lang());
 459        language_registry.add(rust_lang());
 460
 461        let mut cx = EditorTestContext::new(cx).await;
 462        cx.update_buffer(|buffer, cx| {
 463            buffer.set_language_registry(language_registry.clone());
 464            buffer.set_language(Some(markdown_lang()), cx);
 465        });
 466
 467        cx.set_state(indoc! {r#"
 468            fn main() {
 469                let v: Vec<Stringˇ> = vec![];
 470            }
 471        "#});
 472        cx.executor().advance_clock(Duration::from_millis(100));
 473        cx.executor().run_until_parked();
 474
 475        assert_eq!(
 476            r#"fn main«1()1» «1{
 477    let v: Vec<String> = vec!«2[]2»;
 478}1»
 479
 4801 hsla(207.80, 16.20%, 69.19%, 1.00)
 4812 hsla(29.00, 54.00%, 65.88%, 1.00)
 482"#,
 483            &bracket_colors_markup(&mut cx),
 484            "Markdown does not colorize <> brackets"
 485        );
 486
 487        cx.update_buffer(|buffer, cx| {
 488            buffer.set_language(Some(rust_lang()), cx);
 489        });
 490        cx.executor().advance_clock(Duration::from_millis(100));
 491        cx.executor().run_until_parked();
 492
 493        assert_eq!(
 494            r#"fn main«1()1» «1{
 495    let v: Vec«2<String>2» = vec!«2[]2»;
 496}1»
 497
 4981 hsla(207.80, 16.20%, 69.19%, 1.00)
 4992 hsla(29.00, 54.00%, 65.88%, 1.00)
 500"#,
 501            &bracket_colors_markup(&mut cx),
 502            "After switching to Rust, <> brackets are now colorized"
 503        );
 504    }
 505
 506    #[gpui::test]
 507    async fn test_bracket_colorization_when_editing(cx: &mut gpui::TestAppContext) {
 508        init_test(cx, |language_settings| {
 509            language_settings.defaults.colorize_brackets = Some(true);
 510        });
 511        let mut cx = EditorLspTestContext::new(
 512            Arc::into_inner(rust_lang()).unwrap(),
 513            lsp::ServerCapabilities::default(),
 514            cx,
 515        )
 516        .await;
 517
 518        cx.set_state(indoc! {r#"
 519struct Foo<'a, T> {
 520    data: Vec<Option<&'a T>>,
 521}
 522
 523fn process_data() {
 524    let map:ˇ
 525}
 526"#});
 527
 528        cx.update_editor(|editor, window, cx| {
 529            editor.handle_input(" Result<", window, cx);
 530        });
 531        cx.executor().advance_clock(Duration::from_millis(100));
 532        cx.executor().run_until_parked();
 533        assert_eq!(
 534            indoc! {r#"
 535struct Foo«1<'a, T>1» «1{
 536    data: Vec«2<Option«3<&'a T>3»>2»,
 537}1»
 538
 539fn process_data«1()1» «1{
 540    let map: Result<
 541}1»
 542
 5431 hsla(207.80, 16.20%, 69.19%, 1.00)
 5442 hsla(29.00, 54.00%, 65.88%, 1.00)
 5453 hsla(286.00, 51.00%, 75.25%, 1.00)
 546"#},
 547            &bracket_colors_markup(&mut cx),
 548            "Brackets without pairs should be ignored and not colored"
 549        );
 550
 551        cx.update_editor(|editor, window, cx| {
 552            editor.handle_input("Option<Foo<'_, ()", window, cx);
 553        });
 554        cx.executor().advance_clock(Duration::from_millis(100));
 555        cx.executor().run_until_parked();
 556        assert_eq!(
 557            indoc! {r#"
 558struct Foo«1<'a, T>1» «1{
 559    data: Vec«2<Option«3<&'a T>3»>2»,
 560}1»
 561
 562fn process_data«1()1» «1{
 563    let map: Result<Option<Foo<'_, «2()2»
 564}1»
 565
 5661 hsla(207.80, 16.20%, 69.19%, 1.00)
 5672 hsla(29.00, 54.00%, 65.88%, 1.00)
 5683 hsla(286.00, 51.00%, 75.25%, 1.00)
 569"#},
 570            &bracket_colors_markup(&mut cx),
 571        );
 572
 573        cx.update_editor(|editor, window, cx| {
 574            editor.handle_input(">", window, cx);
 575        });
 576        cx.executor().advance_clock(Duration::from_millis(100));
 577        cx.executor().run_until_parked();
 578        assert_eq!(
 579            indoc! {r#"
 580struct Foo«1<'a, T>1» «1{
 581    data: Vec«2<Option«3<&'a T>3»>2»,
 582}1»
 583
 584fn process_data«1()1» «1{
 585    let map: Result<Option<Foo«2<'_, «3()3»>2»
 586}1»
 587
 5881 hsla(207.80, 16.20%, 69.19%, 1.00)
 5892 hsla(29.00, 54.00%, 65.88%, 1.00)
 5903 hsla(286.00, 51.00%, 75.25%, 1.00)
 591"#},
 592            &bracket_colors_markup(&mut cx),
 593            "When brackets start to get closed, inner brackets are re-colored based on their depth"
 594        );
 595
 596        cx.update_editor(|editor, window, cx| {
 597            editor.handle_input(">", window, cx);
 598        });
 599        cx.executor().advance_clock(Duration::from_millis(100));
 600        cx.executor().run_until_parked();
 601        assert_eq!(
 602            indoc! {r#"
 603struct Foo«1<'a, T>1» «1{
 604    data: Vec«2<Option«3<&'a T>3»>2»,
 605}1»
 606
 607fn process_data«1()1» «1{
 608    let map: Result<Option«2<Foo«3<'_, «4()4»>3»>2»
 609}1»
 610
 6111 hsla(207.80, 16.20%, 69.19%, 1.00)
 6122 hsla(29.00, 54.00%, 65.88%, 1.00)
 6133 hsla(286.00, 51.00%, 75.25%, 1.00)
 6144 hsla(187.00, 47.00%, 59.22%, 1.00)
 615"#},
 616            &bracket_colors_markup(&mut cx),
 617        );
 618
 619        cx.update_editor(|editor, window, cx| {
 620            editor.handle_input(", ()> = unimplemented!();", window, cx);
 621        });
 622        cx.executor().advance_clock(Duration::from_millis(100));
 623        cx.executor().run_until_parked();
 624        assert_eq!(
 625            indoc! {r#"
 626struct Foo«1<'a, T>1» «1{
 627    data: Vec«2<Option«3<&'a T>3»>2»,
 628}1»
 629
 630fn process_data«1()1» «1{
 631    let map: Result«2<Option«3<Foo«4<'_, «5()5»>4»>3», «3()3»>2» = unimplemented!«2()2»;
 632}1»
 633
 6341 hsla(207.80, 16.20%, 69.19%, 1.00)
 6352 hsla(29.00, 54.00%, 65.88%, 1.00)
 6363 hsla(286.00, 51.00%, 75.25%, 1.00)
 6374 hsla(187.00, 47.00%, 59.22%, 1.00)
 6385 hsla(355.00, 65.00%, 75.94%, 1.00)
 639"#},
 640            &bracket_colors_markup(&mut cx),
 641        );
 642    }
 643
 644    #[gpui::test]
 645    async fn test_bracket_colorization_chunks(cx: &mut gpui::TestAppContext) {
 646        let comment_lines = 100;
 647
 648        init_test(cx, |language_settings| {
 649            language_settings.defaults.colorize_brackets = Some(true);
 650        });
 651        let mut cx = EditorLspTestContext::new(
 652            Arc::into_inner(rust_lang()).unwrap(),
 653            lsp::ServerCapabilities::default(),
 654            cx,
 655        )
 656        .await;
 657
 658        cx.set_state(&separate_with_comment_lines(
 659            indoc! {r#"
 660mod foo {
 661    ˇfn process_data_1() {
 662        let map: Option<Vec<()>> = None;
 663    }
 664"#},
 665            indoc! {r#"
 666    fn process_data_2() {
 667        let map: Option<Vec<()>> = None;
 668    }
 669}
 670"#},
 671            comment_lines,
 672        ));
 673
 674        cx.executor().advance_clock(Duration::from_millis(100));
 675        cx.executor().run_until_parked();
 676        assert_eq!(
 677            &separate_with_comment_lines(
 678                indoc! {r#"
 679mod foo «1{
 680    fn process_data_1«2()2» «2{
 681        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 682    }2»
 683"#},
 684                indoc! {r#"
 685    fn process_data_2() {
 686        let map: Option<Vec<()>> = None;
 687    }
 688}1»
 689
 6901 hsla(207.80, 16.20%, 69.19%, 1.00)
 6912 hsla(29.00, 54.00%, 65.88%, 1.00)
 6923 hsla(286.00, 51.00%, 75.25%, 1.00)
 6934 hsla(187.00, 47.00%, 59.22%, 1.00)
 6945 hsla(355.00, 65.00%, 75.94%, 1.00)
 695"#},
 696                comment_lines,
 697            ),
 698            &bracket_colors_markup(&mut cx),
 699            "First, the only visible chunk is getting the bracket highlights"
 700        );
 701
 702        cx.update_editor(|editor, window, cx| {
 703            editor.move_to_end(&MoveToEnd, window, cx);
 704            editor.move_up(&MoveUp, window, cx);
 705        });
 706        cx.executor().advance_clock(Duration::from_millis(100));
 707        cx.executor().run_until_parked();
 708        assert_eq!(
 709            &separate_with_comment_lines(
 710                indoc! {r#"
 711mod foo «1{
 712    fn process_data_1«2()2» «2{
 713        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 714    }2»
 715"#},
 716                indoc! {r#"
 717    fn process_data_2«2()2» «2{
 718        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 719    }2»
 720}1»
 721
 7221 hsla(207.80, 16.20%, 69.19%, 1.00)
 7232 hsla(29.00, 54.00%, 65.88%, 1.00)
 7243 hsla(286.00, 51.00%, 75.25%, 1.00)
 7254 hsla(187.00, 47.00%, 59.22%, 1.00)
 7265 hsla(355.00, 65.00%, 75.94%, 1.00)
 727"#},
 728                comment_lines,
 729            ),
 730            &bracket_colors_markup(&mut cx),
 731            "After scrolling to the bottom, both chunks should have the highlights"
 732        );
 733
 734        cx.update_editor(|editor, window, cx| {
 735            editor.handle_input("{{}}}", window, cx);
 736        });
 737        cx.executor().advance_clock(Duration::from_millis(100));
 738        cx.executor().run_until_parked();
 739        assert_eq!(
 740            &separate_with_comment_lines(
 741                indoc! {r#"
 742mod foo «1{
 743    fn process_data_1() {
 744        let map: Option<Vec<()>> = None;
 745    }
 746"#},
 747                indoc! {r#"
 748    fn process_data_2«2()2» «2{
 749        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 750    }
 751    «3{«4{}4»}3»}2»}1»
 752
 7531 hsla(207.80, 16.20%, 69.19%, 1.00)
 7542 hsla(29.00, 54.00%, 65.88%, 1.00)
 7553 hsla(286.00, 51.00%, 75.25%, 1.00)
 7564 hsla(187.00, 47.00%, 59.22%, 1.00)
 7575 hsla(355.00, 65.00%, 75.94%, 1.00)
 758"#},
 759                comment_lines,
 760            ),
 761            &bracket_colors_markup(&mut cx),
 762            "First chunk's brackets are invalidated after an edit, and only 2nd (visible) chunk is re-colorized"
 763        );
 764
 765        cx.update_editor(|editor, window, cx| {
 766            editor.move_to_beginning(&MoveToBeginning, window, cx);
 767        });
 768        cx.executor().advance_clock(Duration::from_millis(100));
 769        cx.executor().run_until_parked();
 770        assert_eq!(
 771            &separate_with_comment_lines(
 772                indoc! {r#"
 773mod foo «1{
 774    fn process_data_1«2()2» «2{
 775        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 776    }2»
 777"#},
 778                indoc! {r#"
 779    fn process_data_2«2()2» «2{
 780        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 781    }
 782    «3{«4{}4»}3»}2»}1»
 783
 7841 hsla(207.80, 16.20%, 69.19%, 1.00)
 7852 hsla(29.00, 54.00%, 65.88%, 1.00)
 7863 hsla(286.00, 51.00%, 75.25%, 1.00)
 7874 hsla(187.00, 47.00%, 59.22%, 1.00)
 7885 hsla(355.00, 65.00%, 75.94%, 1.00)
 789"#},
 790                comment_lines,
 791            ),
 792            &bracket_colors_markup(&mut cx),
 793            "Scrolling back to top should re-colorize all chunks' brackets"
 794        );
 795
 796        cx.update(|_, cx| {
 797            SettingsStore::update_global(cx, |store, cx| {
 798                store.update_user_settings(cx, |settings| {
 799                    settings.project.all_languages.defaults.colorize_brackets = Some(false);
 800                });
 801            });
 802        });
 803        cx.executor().run_until_parked();
 804        assert_eq!(
 805            &separate_with_comment_lines(
 806                indoc! {r#"
 807mod foo {
 808    fn process_data_1() {
 809        let map: Option<Vec<()>> = None;
 810    }
 811"#},
 812                r#"    fn process_data_2() {
 813        let map: Option<Vec<()>> = None;
 814    }
 815    {{}}}}
 816
 817"#,
 818                comment_lines,
 819            ),
 820            &bracket_colors_markup(&mut cx),
 821            "Turning bracket colorization off should remove all bracket colors"
 822        );
 823
 824        cx.update(|_, cx| {
 825            SettingsStore::update_global(cx, |store, cx| {
 826                store.update_user_settings(cx, |settings| {
 827                    settings.project.all_languages.defaults.colorize_brackets = Some(true);
 828                });
 829            });
 830        });
 831        cx.executor().run_until_parked();
 832        assert_eq!(
 833            &separate_with_comment_lines(
 834                indoc! {r#"
 835mod foo «1{
 836    fn process_data_1«2()2» «2{
 837        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
 838    }2»
 839"#},
 840                r#"    fn process_data_2() {
 841        let map: Option<Vec<()>> = None;
 842    }
 843    {{}}}}1»
 844
 8451 hsla(207.80, 16.20%, 69.19%, 1.00)
 8462 hsla(29.00, 54.00%, 65.88%, 1.00)
 8473 hsla(286.00, 51.00%, 75.25%, 1.00)
 8484 hsla(187.00, 47.00%, 59.22%, 1.00)
 8495 hsla(355.00, 65.00%, 75.94%, 1.00)
 850"#,
 851                comment_lines,
 852            ),
 853            &bracket_colors_markup(&mut cx),
 854            "Turning bracket colorization back on refreshes the visible excerpts' bracket colors"
 855        );
 856    }
 857
 858    #[gpui::test]
 859    async fn test_rainbow_bracket_highlights(cx: &mut gpui::TestAppContext) {
 860        init_test(cx, |language_settings| {
 861            language_settings.defaults.colorize_brackets = Some(true);
 862        });
 863        let mut cx = EditorLspTestContext::new(
 864            Arc::into_inner(rust_lang()).unwrap(),
 865            lsp::ServerCapabilities::default(),
 866            cx,
 867        )
 868        .await;
 869
 870        // taken from r-a https://github.com/rust-lang/rust-analyzer/blob/d733c07552a2dc0ec0cc8f4df3f0ca969a93fd90/crates/ide/src/inlay_hints.rs#L81-L297
 871        cx.set_state(indoc! {r# 872            pub(crate) fn inlay_hints(
 873                db: &RootDatabase,
 874                file_id: FileId,
 875                range_limit: Option<TextRange>,
 876                config: &InlayHintsConfig,
 877            ) -> Vec<InlayHint> {
 878                let _p = tracing::info_span!("inlay_hints").entered();
 879                let sema = Semantics::new(db);
 880                let file_id = sema
 881                    .attach_first_edition(file_id)
 882                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 883                let file = sema.parse(file_id);
 884                let file = file.syntax();
 885
 886                let mut acc = Vec::new();
 887
 888                let Some(scope) = sema.scope(file) else {
 889                    return acc;
 890                };
 891                let famous_defs = FamousDefs(&sema, scope.krate());
 892                let display_target = famous_defs.1.to_display_target(sema.db);
 893
 894                let ctx = &mut InlayHintCtx::default();
 895                let mut hints = |event| {
 896                    if let Some(node) = handle_event(ctx, event) {
 897                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 898                    }
 899                };
 900                let mut preorder = file.preorder();
 901                salsa::attach(sema.db, || {
 902                    while let Some(event) = preorder.next() {
 903                        if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
 904                        {
 905                            preorder.skip_subtree();
 906                            continue;
 907                        }
 908                        hints(event);
 909                    }
 910                });
 911                if let Some(range_limit) = range_limit {
 912                    acc.retain(|hint| range_limit.contains_range(hint.range));
 913                }
 914                acc
 915            }
 916
 917            #[derive(Default)]
 918            struct InlayHintCtx {
 919                lifetime_stacks: Vec<Vec<SmolStr>>,
 920                extern_block_parent: Option<ast::ExternBlock>,
 921            }
 922
 923            pub(crate) fn inlay_hints_resolve(
 924                db: &RootDatabase,
 925                file_id: FileId,
 926                resolve_range: TextRange,
 927                hash: u64,
 928                config: &InlayHintsConfig,
 929                hasher: impl Fn(&InlayHint) -> u64,
 930            ) -> Option<InlayHint> {
 931                let _p = tracing::info_span!("inlay_hints_resolve").entered();
 932                let sema = Semantics::new(db);
 933                let file_id = sema
 934                    .attach_first_edition(file_id)
 935                    .unwrap_or_else(|| EditionedFileId::current_edition(db, file_id));
 936                let file = sema.parse(file_id);
 937                let file = file.syntax();
 938
 939                let scope = sema.scope(file)?;
 940                let famous_defs = FamousDefs(&sema, scope.krate());
 941                let mut acc = Vec::new();
 942
 943                let display_target = famous_defs.1.to_display_target(sema.db);
 944
 945                let ctx = &mut InlayHintCtx::default();
 946                let mut hints = |event| {
 947                    if let Some(node) = handle_event(ctx, event) {
 948                        hints(&mut acc, ctx, &famous_defs, config, file_id, display_target, node);
 949                    }
 950                };
 951
 952                let mut preorder = file.preorder();
 953                while let Some(event) = preorder.next() {
 954                    // This can miss some hints that require the parent of the range to calculate
 955                    if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
 956                    {
 957                        preorder.skip_subtree();
 958                        continue;
 959                    }
 960                    hints(event);
 961                }
 962                acc.into_iter().find(|hint| hasher(hint) == hash)
 963            }
 964
 965            fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
 966                match node {
 967                    WalkEvent::Enter(node) => {
 968                        if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
 969                            let params = node
 970                                .generic_param_list()
 971                                .map(|it| {
 972                                    it.lifetime_params()
 973                                        .filter_map(|it| {
 974                                            it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
 975                                        })
 976                                        .collect()
 977                                })
 978                                .unwrap_or_default();
 979                            ctx.lifetime_stacks.push(params);
 980                        }
 981                        if let Some(node) = ast::ExternBlock::cast(node.clone()) {
 982                            ctx.extern_block_parent = Some(node);
 983                        }
 984                        Some(node)
 985                    }
 986                    WalkEvent::Leave(n) => {
 987                        if ast::AnyHasGenericParams::can_cast(n.kind()) {
 988                            ctx.lifetime_stacks.pop();
 989                        }
 990                        if ast::ExternBlock::can_cast(n.kind()) {
 991                            ctx.extern_block_parent = None;
 992                        }
 993                        None
 994                    }
 995                }
 996            }
 997
 998            // At some point when our hir infra is fleshed out enough we should flip this and traverse the
 999            // HIR instead of the syntax tree.
1000            fn hints(
1001                hints: &mut Vec<InlayHint>,
1002                ctx: &mut InlayHintCtx,
1003                famous_defs @ FamousDefs(sema, _krate): &FamousDefs<'_, '_>,
1004                config: &InlayHintsConfig,
1005                file_id: EditionedFileId,
1006                display_target: DisplayTarget,
1007                node: SyntaxNode,
1008            ) {
1009                closing_brace::hints(
1010                    hints,
1011                    sema,
1012                    config,
1013                    display_target,
1014                    InRealFile { file_id, value: node.clone() },
1015                );
1016                if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
1017                    generic_param::hints(hints, famous_defs, config, any_has_generic_args);
1018                }
1019
1020                match_ast! {
1021                    match node {
1022                        ast::Expr(expr) => {
1023                            chaining::hints(hints, famous_defs, config, display_target, &expr);
1024                            adjustment::hints(hints, famous_defs, config, display_target, &expr);
1025                            match expr {
1026                                ast::Expr::CallExpr(it) => param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it)),
1027                                ast::Expr::MethodCallExpr(it) => {
1028                                    param_name::hints(hints, famous_defs, config, file_id, ast::Expr::from(it))
1029                                }
1030                                ast::Expr::ClosureExpr(it) => {
1031                                    closure_captures::hints(hints, famous_defs, config, it.clone());
1032                                    closure_ret::hints(hints, famous_defs, config, display_target, it)
1033                                },
1034                                ast::Expr::RangeExpr(it) => range_exclusive::hints(hints, famous_defs, config, it),
1035                                _ => Some(()),
1036                            }
1037                        },
1038                        ast::Pat(it) => {
1039                            binding_mode::hints(hints, famous_defs, config, &it);
1040                            match it {
1041                                ast::Pat::IdentPat(it) => {
1042                                    bind_pat::hints(hints, famous_defs, config, display_target, &it);
1043                                }
1044                                ast::Pat::RangePat(it) => {
1045                                    range_exclusive::hints(hints, famous_defs, config, it);
1046                                }
1047                                _ => {}
1048                            }
1049                            Some(())
1050                        },
1051                        ast::Item(it) => match it {
1052                            ast::Item::Fn(it) => {
1053                                implicit_drop::hints(hints, famous_defs, config, display_target, &it);
1054                                if let Some(extern_block) = &ctx.extern_block_parent {
1055                                    extern_block::fn_hints(hints, famous_defs, config, &it, extern_block);
1056                                }
1057                                lifetime::fn_hints(hints, ctx, famous_defs, config,  it)
1058                            },
1059                            ast::Item::Static(it) => {
1060                                if let Some(extern_block) = &ctx.extern_block_parent {
1061                                    extern_block::static_hints(hints, famous_defs, config, &it, extern_block);
1062                                }
1063                                implicit_static::hints(hints, famous_defs, config,  Either::Left(it))
1064                            },
1065                            ast::Item::Const(it) => implicit_static::hints(hints, famous_defs, config, Either::Right(it)),
1066                            ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, it),
1067                            ast::Item::ExternBlock(it) => extern_block::extern_block_hints(hints, famous_defs, config, it),
1068                            _ => None,
1069                        },
1070                        // trait object type elisions
1071                        ast::Type(ty) => match ty {
1072                            ast::Type::FnPtrType(ptr) => lifetime::fn_ptr_hints(hints, ctx, famous_defs, config,  ptr),
1073                            ast::Type::PathType(path) => {
1074                                lifetime::fn_path_hints(hints, ctx, famous_defs, config, &path);
1075                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Left(path));
1076                                Some(())
1077                            },
1078                            ast::Type::DynTraitType(dyn_) => {
1079                                implied_dyn_trait::hints(hints, famous_defs, config, Either::Right(dyn_));
1080                                Some(())
1081                            },
1082                            _ => Some(()),
1083                        },
1084                        ast::GenericParamList(it) => bounds::hints(hints, famous_defs, config,  it),
1085                        _ => Some(()),
1086                    }
1087                };
1088            }
1089        "#});
1090        cx.executor().advance_clock(Duration::from_millis(100));
1091        cx.executor().run_until_parked();
1092
1093        let actual_ranges = cx.update_editor(|editor, window, cx| {
1094            editor
1095                .snapshot(window, cx)
1096                .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1097        });
1098
1099        let mut highlighted_brackets = HashMap::default();
1100        for (color, range) in actual_ranges.iter().cloned() {
1101            highlighted_brackets.insert(range, color);
1102        }
1103
1104        let last_bracket = actual_ranges
1105            .iter()
1106            .max_by_key(|(_, p)| p.end.row)
1107            .unwrap()
1108            .clone();
1109
1110        cx.update_editor(|editor, window, cx| {
1111            let was_scrolled = editor.set_scroll_position(
1112                gpui::Point::new(0.0, last_bracket.1.end.row as f64 * 2.0),
1113                window,
1114                cx,
1115            );
1116            assert!(was_scrolled.0);
1117        });
1118        cx.executor().advance_clock(Duration::from_millis(100));
1119        cx.executor().run_until_parked();
1120
1121        let ranges_after_scrolling = cx.update_editor(|editor, window, cx| {
1122            editor
1123                .snapshot(window, cx)
1124                .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)))
1125        });
1126        let new_last_bracket = ranges_after_scrolling
1127            .iter()
1128            .max_by_key(|(_, p)| p.end.row)
1129            .unwrap()
1130            .clone();
1131
1132        assert_ne!(
1133            last_bracket, new_last_bracket,
1134            "After scrolling down, we should have highlighted more brackets"
1135        );
1136
1137        cx.update_editor(|editor, window, cx| {
1138            let was_scrolled = editor.set_scroll_position(gpui::Point::default(), window, cx);
1139            assert!(was_scrolled.0);
1140        });
1141
1142        for _ in 0..200 {
1143            cx.update_editor(|editor, window, cx| {
1144                editor.apply_scroll_delta(gpui::Point::new(0.0, 0.25), window, cx);
1145            });
1146            cx.executor().advance_clock(Duration::from_millis(100));
1147            cx.executor().run_until_parked();
1148
1149            let colored_brackets = cx.update_editor(|editor, window, cx| {
1150                editor
1151                    .snapshot(window, cx)
1152                    .all_text_highlight_ranges(&|key| {
1153                        matches!(key, HighlightKey::ColorizeBracket(_))
1154                    })
1155            });
1156            for (color, range) in colored_brackets.clone() {
1157                assert!(
1158                    highlighted_brackets.entry(range).or_insert(color) == &color,
1159                    "Colors should stay consistent while scrolling!"
1160                );
1161            }
1162
1163            let snapshot = cx.update_editor(|editor, window, cx| editor.snapshot(window, cx));
1164            let scroll_position = snapshot.scroll_position();
1165            let visible_lines =
1166                cx.update_editor(|editor, _, _| editor.visible_line_count().unwrap());
1167            let visible_range = DisplayRow(scroll_position.y as u32)
1168                ..DisplayRow((scroll_position.y + visible_lines) as u32);
1169
1170            let current_highlighted_bracket_set: HashSet<Point> = HashSet::from_iter(
1171                colored_brackets
1172                    .iter()
1173                    .flat_map(|(_, range)| [range.start, range.end]),
1174            );
1175
1176            for highlight_range in highlighted_brackets.keys().filter(|bracket_range| {
1177                visible_range.contains(&bracket_range.start.to_display_point(&snapshot).row())
1178                    || visible_range.contains(&bracket_range.end.to_display_point(&snapshot).row())
1179            }) {
1180                assert!(
1181                    current_highlighted_bracket_set.contains(&highlight_range.start)
1182                        || current_highlighted_bracket_set.contains(&highlight_range.end),
1183                    "Should not lose highlights while scrolling in the visible range!"
1184                );
1185            }
1186
1187            let buffer_snapshot = snapshot.buffer().as_singleton().unwrap();
1188            for bracket_match in buffer_snapshot
1189                .fetch_bracket_ranges(
1190                    snapshot
1191                        .display_point_to_point(
1192                            DisplayPoint::new(visible_range.start, 0),
1193                            Bias::Left,
1194                        )
1195                        .to_offset(&buffer_snapshot)
1196                        ..snapshot
1197                            .display_point_to_point(
1198                                DisplayPoint::new(
1199                                    visible_range.end,
1200                                    snapshot.line_len(visible_range.end),
1201                                ),
1202                                Bias::Right,
1203                            )
1204                            .to_offset(&buffer_snapshot),
1205                    None,
1206                )
1207                .iter()
1208                .flat_map(|entry| entry.1)
1209                .filter(|bracket_match| bracket_match.color_index.is_some())
1210            {
1211                let start = bracket_match.open_range.to_point(buffer_snapshot);
1212                let end = bracket_match.close_range.to_point(buffer_snapshot);
1213                let start_bracket = colored_brackets.iter().find(|(_, range)| *range == start);
1214                assert!(
1215                    start_bracket.is_some(),
1216                    "Existing bracket start in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1217                    buffer_snapshot
1218                        .text_for_range(start.start..end.end)
1219                        .collect::<String>(),
1220                    start
1221                );
1222
1223                let end_bracket = colored_brackets.iter().find(|(_, range)| *range == end);
1224                assert!(
1225                    end_bracket.is_some(),
1226                    "Existing bracket end in the visible range should be highlighted. Missing color for match: \"{}\" at position {:?}",
1227                    buffer_snapshot
1228                        .text_for_range(start.start..end.end)
1229                        .collect::<String>(),
1230                    start
1231                );
1232
1233                assert_eq!(
1234                    start_bracket.unwrap().0,
1235                    end_bracket.unwrap().0,
1236                    "Bracket pair should be highlighted the same color!"
1237                )
1238            }
1239        }
1240    }
1241
1242    #[gpui::test]
1243    async fn test_multi_buffer(cx: &mut gpui::TestAppContext) {
1244        let comment_lines = 100;
1245
1246        init_test(cx, |language_settings| {
1247            language_settings.defaults.colorize_brackets = Some(true);
1248        });
1249        let fs = FakeFs::new(cx.background_executor.clone());
1250        fs.insert_tree(
1251            path!("/a"),
1252            json!({
1253                "main.rs": "fn main() {{()}}",
1254                "lib.rs": separate_with_comment_lines(
1255                    indoc! {r#"
1256    mod foo {
1257        fn process_data_1() {
1258            let map: Option<Vec<()>> = None;
1259            // a
1260            // b
1261            // c
1262        }
1263    "#},
1264                    indoc! {r#"
1265        fn process_data_2() {
1266            let other_map: Option<Vec<()>> = None;
1267        }
1268    }
1269    "#},
1270                    comment_lines,
1271                )
1272            }),
1273        )
1274        .await;
1275
1276        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1277        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1278        language_registry.add(rust_lang());
1279
1280        let buffer_1 = project
1281            .update(cx, |project, cx| {
1282                project.open_local_buffer(path!("/a/lib.rs"), cx)
1283            })
1284            .await
1285            .unwrap();
1286        let buffer_2 = project
1287            .update(cx, |project, cx| {
1288                project.open_local_buffer(path!("/a/main.rs"), cx)
1289            })
1290            .await
1291            .unwrap();
1292
1293        let multi_buffer = cx.new(|cx| {
1294            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1295            multi_buffer.set_excerpts_for_path(
1296                PathKey::sorted(0),
1297                buffer_2.clone(),
1298                [Point::new(0, 0)..Point::new(1, 0)],
1299                0,
1300                cx,
1301            );
1302
1303            let excerpt_rows = 5;
1304            let rest_of_first_except_rows = 3;
1305            multi_buffer.set_excerpts_for_path(
1306                PathKey::sorted(1),
1307                buffer_1.clone(),
1308                [
1309                    Point::new(0, 0)..Point::new(excerpt_rows, 0),
1310                    Point::new(
1311                        comment_lines as u32 + excerpt_rows + rest_of_first_except_rows,
1312                        0,
1313                    )
1314                        ..Point::new(
1315                            comment_lines as u32
1316                                + excerpt_rows
1317                                + rest_of_first_except_rows
1318                                + excerpt_rows,
1319                            0,
1320                        ),
1321                ],
1322                0,
1323                cx,
1324            );
1325            multi_buffer
1326        });
1327
1328        let editor = cx.add_window(|window, cx| {
1329            Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1330        });
1331        cx.executor().advance_clock(Duration::from_millis(100));
1332        cx.executor().run_until_parked();
1333
1334        let editor_snapshot = editor
1335            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1336            .unwrap();
1337        assert_eq!(
1338            indoc! {r#"
1339
1340
1341fn main«1()1» «1{«2{«3()3»}2»}1»
1342
1343
1344mod foo «1{
1345    fn process_data_1«2()2» «2{
1346        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1347        // a
1348        // b
1349        // c
1350
1351    fn process_data_2«2()2» «2{
1352        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1353    }2»
1354}1»
1355
13561 hsla(207.80, 16.20%, 69.19%, 1.00)
13572 hsla(29.00, 54.00%, 65.88%, 1.00)
13583 hsla(286.00, 51.00%, 75.25%, 1.00)
13594 hsla(187.00, 47.00%, 59.22%, 1.00)
13605 hsla(355.00, 65.00%, 75.94%, 1.00)
1361"#,},
1362            &editor_bracket_colors_markup(&editor_snapshot),
1363            "Multi buffers should have their brackets colored even if no excerpts contain the bracket counterpart (after fn `process_data_2()`) \
1364or if the buffer pair spans across multiple excerpts (the one after `mod foo`)"
1365        );
1366
1367        editor
1368            .update(cx, |editor, window, cx| {
1369                editor.handle_input("{[]", window, cx);
1370            })
1371            .unwrap();
1372        cx.executor().advance_clock(Duration::from_millis(100));
1373        cx.executor().run_until_parked();
1374        let editor_snapshot = editor
1375            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1376            .unwrap();
1377        assert_eq!(
1378            indoc! {r#"
1379
1380
1381{«1[]1»fn main«1()1» «1{«2{«3()3»}2»}1»
1382
1383
1384mod foo «1{
1385    fn process_data_1«2()2» «2{
1386        let map: Option«3<Vec«4<«5()5»>4»>3» = None;
1387        // a
1388        // b
1389        // c
1390
1391    fn process_data_2«2()2» «2{
1392        let other_map: Option«3<Vec«4<«5()5»>4»>3» = None;
1393    }2»
1394}1»
1395
13961 hsla(207.80, 16.20%, 69.19%, 1.00)
13972 hsla(29.00, 54.00%, 65.88%, 1.00)
13983 hsla(286.00, 51.00%, 75.25%, 1.00)
13994 hsla(187.00, 47.00%, 59.22%, 1.00)
14005 hsla(355.00, 65.00%, 75.94%, 1.00)
1401"#,},
1402            &editor_bracket_colors_markup(&editor_snapshot),
1403        );
1404
1405        cx.update(|cx| {
1406            let theme = cx.theme().name.clone();
1407            SettingsStore::update_global(cx, |store, cx| {
1408                store.update_user_settings(cx, |settings| {
1409                    settings.theme.theme_overrides = HashMap::from_iter([(
1410                        theme.to_string(),
1411                        ThemeStyleContent {
1412                            accents: vec![
1413                                AccentContent(Some("#ff0000".to_string())),
1414                                AccentContent(Some("#0000ff".to_string())),
1415                            ],
1416                            ..ThemeStyleContent::default()
1417                        },
1418                    )]);
1419                });
1420            });
1421        });
1422        cx.executor().advance_clock(Duration::from_millis(100));
1423        cx.executor().run_until_parked();
1424        let editor_snapshot = editor
1425            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1426            .unwrap();
1427        assert_eq!(
1428            indoc! {r#"
1429
1430
1431{«1[]1»fn main«1()1» «1{«2{«1()1»}2»}1»
1432
1433
1434mod foo «1{
1435    fn process_data_1«2()2» «2{
1436        let map: Option«1<Vec«2<«1()1»>2»>1» = None;
1437        // a
1438        // b
1439        // c
1440
1441    fn process_data_2«2()2» «2{
1442        let other_map: Option«1<Vec«2<«1()1»>2»>1» = None;
1443    }2»
1444}1»
1445
14461 hsla(0.00, 100.00%, 78.12%, 1.00)
14472 hsla(240.00, 100.00%, 82.81%, 1.00)
1448"#,},
1449            &editor_bracket_colors_markup(&editor_snapshot),
1450            "After updating theme accents, the editor should update the bracket coloring"
1451        );
1452    }
1453
1454    #[gpui::test]
1455    async fn test_multi_buffer_close_excerpts(cx: &mut gpui::TestAppContext) {
1456        let comment_lines = 5;
1457
1458        init_test(cx, |language_settings| {
1459            language_settings.defaults.colorize_brackets = Some(true);
1460        });
1461        let fs = FakeFs::new(cx.background_executor.clone());
1462        fs.insert_tree(
1463            path!("/a"),
1464            json!({
1465                "lib.rs": separate_with_comment_lines(
1466                    indoc! {r#"
1467    fn process_data_1() {
1468        let map: Option<Vec<()>> = None;
1469    }
1470    "#},
1471                    indoc! {r#"
1472    fn process_data_2() {
1473        let other_map: Option<Vec<()>> = None;
1474    }
1475    "#},
1476                    comment_lines,
1477                )
1478            }),
1479        )
1480        .await;
1481
1482        let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
1483        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1484        language_registry.add(rust_lang());
1485
1486        let buffer_1 = project
1487            .update(cx, |project, cx| {
1488                project.open_local_buffer(path!("/a/lib.rs"), cx)
1489            })
1490            .await
1491            .unwrap();
1492
1493        let second_excerpt_start = buffer_1.read_with(cx, |buffer, _| {
1494            let text = buffer.text();
1495            text.lines()
1496                .enumerate()
1497                .find(|(_, line)| line.contains("process_data_2"))
1498                .map(|(row, _)| row as u32)
1499                .unwrap()
1500        });
1501
1502        let multi_buffer = cx.new(|cx| {
1503            let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1504            multi_buffer.set_excerpts_for_path(
1505                PathKey::sorted(0),
1506                buffer_1.clone(),
1507                [
1508                    Point::new(0, 0)..Point::new(3, 0),
1509                    Point::new(second_excerpt_start, 0)..Point::new(second_excerpt_start + 3, 0),
1510                ],
1511                0,
1512                cx,
1513            );
1514            multi_buffer
1515        });
1516
1517        let editor = cx.add_window(|window, cx| {
1518            Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
1519        });
1520        cx.executor().advance_clock(Duration::from_millis(100));
1521        cx.executor().run_until_parked();
1522
1523        let editor_snapshot = editor
1524            .update(cx, |editor, window, cx| editor.snapshot(window, cx))
1525            .unwrap();
1526        assert_eq!(
1527            concat!(
1528                "\n",
1529                "\n",
1530                "fn process_data_1\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1531                "    let map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1532                "}1\u{00bb}\n",
1533                "\n",
1534                "\n",
1535                "fn process_data_2\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
1536                "    let other_map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
1537                "}1\u{00bb}\n",
1538                "\n",
1539                "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n",
1540                "2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
1541                "3 hsla(286.00, 51.00%, 75.25%, 1.00)\n",
1542                "4 hsla(187.00, 47.00%, 59.22%, 1.00)\n",
1543            ),
1544            &editor_bracket_colors_markup(&editor_snapshot),
1545            "Two close excerpts from the same buffer (within same tree-sitter chunk) should both have bracket colors"
1546        );
1547    }
1548
1549    #[gpui::test]
1550    // reproduction of #47846
1551    async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
1552        init_test(cx, |language_settings| {
1553            language_settings.defaults.colorize_brackets = Some(true);
1554        });
1555        let mut cx = EditorLspTestContext::new(
1556            Arc::into_inner(rust_lang()).unwrap(),
1557            lsp::ServerCapabilities::default(),
1558            cx,
1559        )
1560        .await;
1561
1562        // Generate a large function body. When folded, this collapses
1563        // to a single display line, making small_function visible on screen.
1564        let mut big_body = String::new();
1565        for i in 0..700 {
1566            big_body.push_str(&format!("    let var_{i:04} = ({i});\n"));
1567        }
1568        let source = format!(
1569            "ˇfn big_function() {{\n{big_body}}}\n\nfn small_function() {{\n    let x = (1, (2, 3));\n}}\n"
1570        );
1571
1572        cx.set_state(&source);
1573        cx.executor().advance_clock(Duration::from_millis(100));
1574        cx.executor().run_until_parked();
1575
1576        cx.update_editor(|editor, window, cx| {
1577            editor.fold_ranges(
1578                vec![Point::new(0, 0)..Point::new(701, 1)],
1579                false,
1580                window,
1581                cx,
1582            );
1583        });
1584        cx.executor().advance_clock(Duration::from_millis(100));
1585        cx.executor().run_until_parked();
1586
1587        assert_eq!(
1588            indoc! {r#"
1589⋯1»
1590
1591fn small_function«1()1» «1{
1592    let x = «2(1, «3(2, 3)3»)2»;
1593}1»
1594
15951 hsla(207.80, 16.20%, 69.19%, 1.00)
15962 hsla(29.00, 54.00%, 65.88%, 1.00)
15973 hsla(286.00, 51.00%, 75.25%, 1.00)
1598"#,},
1599            bracket_colors_markup(&mut cx),
1600        );
1601    }
1602
1603    fn separate_with_comment_lines(head: &str, tail: &str, comment_lines: usize) -> String {
1604        let mut result = head.to_string();
1605        result.push_str("\n");
1606        result.push_str(&"//\n".repeat(comment_lines));
1607        result.push_str(tail);
1608        result
1609    }
1610
1611    fn bracket_colors_markup(cx: &mut EditorTestContext) -> String {
1612        cx.update_editor(|editor, window, cx| {
1613            editor_bracket_colors_markup(&editor.snapshot(window, cx))
1614        })
1615    }
1616
1617    fn editor_bracket_colors_markup(snapshot: &EditorSnapshot) -> String {
1618        fn display_point_to_offset(text: &str, point: DisplayPoint) -> usize {
1619            let mut offset = 0;
1620            for (row_idx, line) in text.lines().enumerate() {
1621                if row_idx < point.row().0 as usize {
1622                    offset += line.len() + 1; // +1 for newline
1623                } else {
1624                    offset += point.column() as usize;
1625                    break;
1626                }
1627            }
1628            offset
1629        }
1630
1631        let actual_ranges = snapshot
1632            .all_text_highlight_ranges(&|key| matches!(key, HighlightKey::ColorizeBracket(_)));
1633        let editor_text = snapshot.text();
1634
1635        let mut next_index = 1;
1636        let mut color_to_index = HashMap::default();
1637        let mut annotations = Vec::new();
1638        for (color, range) in &actual_ranges {
1639            let color_index = *color_to_index
1640                .entry(*color)
1641                .or_insert_with(|| post_inc(&mut next_index));
1642            let start = snapshot.point_to_display_point(range.start, Bias::Left);
1643            let end = snapshot.point_to_display_point(range.end, Bias::Right);
1644            let start_offset = display_point_to_offset(&editor_text, start);
1645            let end_offset = display_point_to_offset(&editor_text, end);
1646            let bracket_text = &editor_text[start_offset..end_offset];
1647            let bracket_char = bracket_text.chars().next().unwrap();
1648
1649            if matches!(bracket_char, '{' | '[' | '(' | '<') {
1650                annotations.push((start_offset, format!("«{color_index}")));
1651            } else {
1652                annotations.push((end_offset, format!("{color_index}»")));
1653            }
1654        }
1655
1656        annotations.sort_by(|(pos_a, text_a), (pos_b, text_b)| {
1657            pos_a.cmp(pos_b).reverse().then_with(|| {
1658                let a_is_opening = text_a.starts_with('«');
1659                let b_is_opening = text_b.starts_with('«');
1660                match (a_is_opening, b_is_opening) {
1661                    (true, false) => cmp::Ordering::Less,
1662                    (false, true) => cmp::Ordering::Greater,
1663                    _ => cmp::Ordering::Equal,
1664                }
1665            })
1666        });
1667        annotations.dedup();
1668
1669        let mut markup = editor_text;
1670        for (offset, text) in annotations {
1671            markup.insert_str(offset, &text);
1672        }
1673
1674        markup.push_str("\n");
1675        for (index, color) in color_to_index
1676            .iter()
1677            .map(|(color, index)| (*index, *color))
1678            .sorted_by_key(|(index, _)| *index)
1679        {
1680            markup.push_str(&format!("{index} {color}\n"));
1681        }
1682
1683        markup
1684    }
1685}