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