highlight_matching_bracket.rs

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