highlight_matching_bracket.rs

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