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