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