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::{editor_tests::init_test, 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 init_test(cx, |_| {});
43
44 let mut cx = EditorLspTestContext::new(
45 Language::new(
46 LanguageConfig {
47 name: "Rust".into(),
48 path_suffixes: vec!["rs".to_string()],
49 brackets: BracketPairConfig {
50 pairs: 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 ..Default::default()
67 },
68 Some(tree_sitter_rust::language()),
69 )
70 .with_brackets_query(indoc! {r#"
71 ("{" @open "}" @close)
72 ("(" @open ")" @close)
73 "#})
74 .unwrap(),
75 Default::default(),
76 cx,
77 )
78 .await;
79
80 // positioning cursor inside bracket highlights both
81 cx.set_state(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
92 cx.set_state(indoc! {r#"
93 pub fn test("Test argument") {
94 another_test(1, ˇ2, 3);
95 }
96 "#});
97 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
98 pub fn test("Test argument") {
99 another_test«(»1, 2, 3«)»;
100 }
101 "#});
102
103 cx.set_state(indoc! {r#"
104 pub fn test("Test argument") {
105 anotherˇ_test(1, 2, 3);
106 }
107 "#});
108 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
109 pub fn test("Test argument") «{»
110 another_test(1, 2, 3);
111 «}»
112 "#});
113
114 // positioning outside of brackets removes highlight
115 cx.set_state(indoc! {r#"
116 pub fˇn test("Test argument") {
117 another_test(1, 2, 3);
118 }
119 "#});
120 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
121 pub fn test("Test argument") {
122 another_test(1, 2, 3);
123 }
124 "#});
125
126 // non empty selection dismisses highlight
127 cx.set_state(indoc! {r#"
128 pub fn test("Te«st argˇ»ument") {
129 another_test(1, 2, 3);
130 }
131 "#});
132 cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
133 pub fn test("Test argument") {
134 another_test(1, 2, 3);
135 }
136 "#});
137 }
138}