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