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 crate::test::editor_lsp_test_context::EditorLspTestContext;
36
37 use super::*;
38 use indoc::indoc;
39 use language::{BracketPair, Language, LanguageConfig};
40
41 #[gpui::test]
42 async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
43 let mut cx = EditorLspTestContext::new(
44 Language::new(
45 LanguageConfig {
46 name: "Rust".into(),
47 path_suffixes: vec!["rs".to_string()],
48 brackets: vec![
49 BracketPair {
50 start: "{".to_string(),
51 end: "}".to_string(),
52 close: false,
53 newline: true,
54 },
55 BracketPair {
56 start: "(".to_string(),
57 end: ")".to_string(),
58 close: false,
59 newline: true,
60 },
61 ],
62 ..Default::default()
63 },
64 Some(tree_sitter_rust::language()),
65 )
66 .with_brackets_query(indoc! {r#"
67 ("{" @open "}" @close)
68 ("(" @open ")" @close)
69 "#})
70 .unwrap(),
71 Default::default(),
72 cx,
73 )
74 .await;
75
76 // positioning cursor inside bracket highlights both
77 cx.set_state(indoc! {r#"
78 pub fn test("Test ˇargument") {
79 another_test(1, 2, 3);
80 }
81 "#});
82 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
83 pub fn test«(»"Test argument"«)» {
84 another_test(1, 2, 3);
85 }
86 "#});
87
88 cx.set_state(indoc! {r#"
89 pub fn test("Test argument") {
90 another_test(1, ˇ2, 3);
91 }
92 "#});
93 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
94 pub fn test("Test argument") {
95 another_test«(»1, 2, 3«)»;
96 }
97 "#});
98
99 cx.set_state(indoc! {r#"
100 pub fn test("Test argument") {
101 anotherˇ_test(1, 2, 3);
102 }
103 "#});
104 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
105 pub fn test("Test argument") «{»
106 another_test(1, 2, 3);
107 «}»
108 "#});
109
110 // positioning outside of brackets removes highlight
111 cx.set_state(indoc! {r#"
112 pub fˇn test("Test argument") {
113 another_test(1, 2, 3);
114 }
115 "#});
116 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
117 pub fn test("Test argument") {
118 another_test(1, 2, 3);
119 }
120 "#});
121
122 // non empty selection dismisses highlight
123 cx.set_state(indoc! {r#"
124 pub fn test("Te«st argˇ»ument") {
125 another_test(1, 2, 3);
126 }
127 "#});
128 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
129 pub fn test("Test argument") {
130 another_test(1, 2, 3);
131 }
132 "#});
133 }
134}