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)
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 super::*;
36 use crate::test::editor_lsp_test_context::EditorLspTestContext;
37 use indoc::indoc;
38 use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
39
40 #[gpui::test]
41 async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
42 let mut cx = EditorLspTestContext::new(
43 Language::new(
44 LanguageConfig {
45 name: "Rust".into(),
46 path_suffixes: vec!["rs".to_string()],
47 brackets: BracketPairConfig {
48 pairs: 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 ..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(indoc! {r#"
80 pub fn test("Test ˇargument") {
81 another_test(1, 2, 3);
82 }
83 "#});
84 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
85 pub fn test«(»"Test argument"«)» {
86 another_test(1, 2, 3);
87 }
88 "#});
89
90 cx.set_state(indoc! {r#"
91 pub fn test("Test argument") {
92 another_test(1, ˇ2, 3);
93 }
94 "#});
95 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
96 pub fn test("Test argument") {
97 another_test«(»1, 2, 3«)»;
98 }
99 "#});
100
101 cx.set_state(indoc! {r#"
102 pub fn test("Test argument") {
103 anotherˇ_test(1, 2, 3);
104 }
105 "#});
106 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
107 pub fn test("Test argument") «{»
108 another_test(1, 2, 3);
109 «}»
110 "#});
111
112 // positioning outside of brackets removes highlight
113 cx.set_state(indoc! {r#"
114 pub fˇn test("Test argument") {
115 another_test(1, 2, 3);
116 }
117 "#});
118 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
119 pub fn test("Test argument") {
120 another_test(1, 2, 3);
121 }
122 "#});
123
124 // non empty selection dismisses highlight
125 cx.set_state(indoc! {r#"
126 pub fn test("Te«st argˇ»ument") {
127 another_test(1, 2, 3);
128 }
129 "#});
130 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
131 pub fn test("Test argument") {
132 another_test(1, 2, 3);
133 }
134 "#});
135 }
136}