highlight_matching_bracket.rs

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