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 newline: true,
59 },
60 BracketPair {
61 start: "(".to_string(),
62 end: ")".to_string(),
63 close: false,
64 newline: true,
65 },
66 ],
67 ..Default::default()
68 },
69 ..Default::default()
70 },
71 Some(tree_sitter_rust::language()),
72 )
73 .with_brackets_query(indoc! {r#"
74 ("{" @open "}" @close)
75 ("(" @open ")" @close)
76 "#})
77 .unwrap(),
78 Default::default(),
79 cx,
80 )
81 .await;
82
83 // positioning cursor inside bracket highlights both
84 cx.set_state(indoc! {r#"
85 pub fn test("Test ˇargument") {
86 another_test(1, 2, 3);
87 }
88 "#});
89 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
90 pub fn test«(»"Test argument"«)» {
91 another_test(1, 2, 3);
92 }
93 "#});
94
95 cx.set_state(indoc! {r#"
96 pub fn test("Test argument") {
97 another_test(1, ˇ2, 3);
98 }
99 "#});
100 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
101 pub fn test("Test argument") {
102 another_test«(»1, 2, 3«)»;
103 }
104 "#});
105
106 cx.set_state(indoc! {r#"
107 pub fn test("Test argument") {
108 anotherˇ_test(1, 2, 3);
109 }
110 "#});
111 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
112 pub fn test("Test argument") «{»
113 another_test(1, 2, 3);
114 «}»
115 "#});
116
117 // positioning outside of brackets removes highlight
118 cx.set_state(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
129 // non empty selection dismisses highlight
130 cx.set_state(indoc! {r#"
131 pub fn test("Te«st argˇ»ument") {
132 another_test(1, 2, 3);
133 }
134 "#});
135 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
136 pub fn test("Test argument") {
137 another_test(1, 2, 3);
138 }
139 "#});
140 }
141}