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