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