highlight_matching_bracket.rs

  1use crate::{Editor, RangeToAnchorExt};
  2use gpui::{Context, Window};
  3use language::CursorShape;
  4
  5enum MatchingBracketHighlight {}
  6
  7pub fn refresh_matching_bracket_highlights(
  8    editor: &mut Editor,
  9    window: &mut Window,
 10    cx: &mut Context<Editor>,
 11) {
 12    editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
 13
 14    let newest_selection = editor.selections.newest::<usize>(cx);
 15    // Don't highlight brackets if the selection isn't empty
 16    if !newest_selection.is_empty() {
 17        return;
 18    }
 19
 20    let snapshot = editor.snapshot(window, cx);
 21    let head = newest_selection.head();
 22    if head > snapshot.buffer_snapshot.len() {
 23        log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
 24        return;
 25    }
 26
 27    let mut tail = head;
 28    if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
 29        && head < snapshot.buffer_snapshot.len()
 30    {
 31        tail += 1;
 32    }
 33
 34    if let Some((opening_range, closing_range)) = snapshot
 35        .buffer_snapshot
 36        .innermost_enclosing_bracket_ranges(head..tail, None)
 37    {
 38        editor.highlight_background::<MatchingBracketHighlight>(
 39            &[
 40                opening_range.to_anchors(&snapshot.buffer_snapshot),
 41                closing_range.to_anchors(&snapshot.buffer_snapshot),
 42            ],
 43            |theme| theme.colors().editor_document_highlight_bracket_background,
 44            cx,
 45        )
 46    }
 47}
 48
 49#[cfg(test)]
 50mod tests {
 51    use super::*;
 52    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
 53    use indoc::indoc;
 54    use language::{BracketPair, BracketPairConfig, Language, LanguageConfig, LanguageMatcher};
 55
 56    #[gpui::test]
 57    async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
 58        init_test(cx, |_| {});
 59
 60        let mut cx = EditorLspTestContext::new(
 61            Language::new(
 62                LanguageConfig {
 63                    name: "Rust".into(),
 64                    matcher: LanguageMatcher {
 65                        path_suffixes: vec!["rs".to_string()],
 66                        ..Default::default()
 67                    },
 68                    brackets: BracketPairConfig {
 69                        pairs: vec![
 70                            BracketPair {
 71                                start: "{".to_string(),
 72                                end: "}".to_string(),
 73                                close: false,
 74                                surround: false,
 75                                newline: true,
 76                            },
 77                            BracketPair {
 78                                start: "(".to_string(),
 79                                end: ")".to_string(),
 80                                close: false,
 81                                surround: false,
 82                                newline: true,
 83                            },
 84                        ],
 85                        ..Default::default()
 86                    },
 87                    ..Default::default()
 88                },
 89                Some(tree_sitter_rust::LANGUAGE.into()),
 90            )
 91            .with_brackets_query(indoc! {r#"
 92                ("{" @open "}" @close)
 93                ("(" @open ")" @close)
 94                "#})
 95            .unwrap(),
 96            Default::default(),
 97            cx,
 98        )
 99        .await;
100
101        // positioning cursor inside bracket highlights both
102        cx.set_state(indoc! {r#"
103            pub fn test("Test ˇargument") {
104                another_test(1, 2, 3);
105            }
106        "#});
107        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
108            pub fn test«(»"Test argument"«)» {
109                another_test(1, 2, 3);
110            }
111        "#});
112
113        cx.set_state(indoc! {r#"
114            pub fn test("Test argument") {
115                another_test(1, ˇ2, 3);
116            }
117        "#});
118        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
119            pub fn test("Test argument") {
120                another_test«(»1, 2, 3«)»;
121            }
122        "#});
123
124        cx.set_state(indoc! {r#"
125            pub fn test("Test argument") {
126                anotherˇ_test(1, 2, 3);
127            }
128        "#});
129        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
130            pub fn test("Test argument") «{»
131                another_test(1, 2, 3);
132            «}»
133        "#});
134
135        // positioning outside of brackets removes highlight
136        cx.set_state(indoc! {r#"
137            pub fˇn test("Test argument") {
138                another_test(1, 2, 3);
139            }
140        "#});
141        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
142            pub fn test("Test argument") {
143                another_test(1, 2, 3);
144            }
145        "#});
146
147        // non empty selection dismisses highlight
148        cx.set_state(indoc! {r#"
149            pub fn test("Te«st argˇ»ument") {
150                another_test(1, 2, 3);
151            }
152        "#});
153        cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
154            pub fn test("Test argument") {
155                another_test(1, 2, 3);
156            }
157        "#});
158    }
159}