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