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