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