bracket_colorization.rs

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